propagate from branch 'i2p.i2p.zzz.test' (head f19c9c4ae55d6ae82d6c028a06c0fae886da2527)
to branch 'i2p.i2p' (head 78d8ece1514216315644bbef224c62e1e9fbe370)
This commit is contained in:
@ -81,8 +81,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
I2PAppThread monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
if (_context instanceof RouterContext)
|
||||
((RouterContext)_context).router().addShutdownTask(new SnarkManagerShutdown());
|
||||
_context.addShutdownTask(new SnarkManagerShutdown());
|
||||
}
|
||||
|
||||
/** hook to I2PSnarkUtil for the servlet */
|
||||
@ -539,7 +538,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
String announce = info.getAnnounce();
|
||||
// basic validation of url
|
||||
if ((!announce.startsWith("http://")) ||
|
||||
(announce.indexOf(".i2p/") < 0))
|
||||
(announce.indexOf(".i2p/") < 0)) // need to do better than this
|
||||
return "Non-i2p tracker in " + info.getName() + ", deleting it";
|
||||
List files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
|
@ -62,6 +62,8 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
|
||||
import net.i2p.i2ptunnel.streamr.StreamrConsumer;
|
||||
import net.i2p.i2ptunnel.streamr.StreamrProducer;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.EventDispatcherImpl;
|
||||
import net.i2p.util.Log;
|
||||
@ -234,6 +236,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runServer(args, l);
|
||||
} else if ("httpserver".equals(cmdname)) {
|
||||
runHttpServer(args, l);
|
||||
} else if ("ircserver".equals(cmdname)) {
|
||||
runIrcServer(args, l);
|
||||
} else if ("textserver".equals(cmdname)) {
|
||||
runTextServer(args, l);
|
||||
} else if ("client".equals(cmdname)) {
|
||||
@ -246,6 +250,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runSOCKSTunnel(args, l);
|
||||
} else if ("connectclient".equals(cmdname)) {
|
||||
runConnectClient(args, l);
|
||||
} else if ("streamrclient".equals(cmdname)) {
|
||||
runStreamrClient(args, l);
|
||||
} else if ("streamrserver".equals(cmdname)) {
|
||||
runStreamrServer(args, l);
|
||||
} else if ("config".equals(cmdname)) {
|
||||
runConfig(args, l);
|
||||
} else if ("listen_on".equals(cmdname)) {
|
||||
@ -383,6 +391,53 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same args as runServer
|
||||
* (we should stop duplicating all this code...)
|
||||
*/
|
||||
public void runIrcServer(String args[], Logging l) {
|
||||
if (args.length == 3) {
|
||||
InetAddress serverHost = null;
|
||||
int portNum = -1;
|
||||
File privKeyFile = null;
|
||||
try {
|
||||
serverHost = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
portNum = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
privKeyFile = new File(args[2]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelServer serv = new I2PTunnelIRCServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
serv.startRunning();
|
||||
addtask(serv);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(serv.getId()));
|
||||
return;
|
||||
} else {
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
l.log(" creates a server that sends all incoming data\n" + " of its destination to host:port.");
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the HTTP server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the specified file, replacing the HTTP headers
|
||||
@ -751,6 +806,82 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streamr client
|
||||
*
|
||||
* @param args {targethost, targetport, destinationString}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runStreamrClient(String args[], Logging l) {
|
||||
if (args.length == 3) {
|
||||
InetAddress host;
|
||||
try {
|
||||
host = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
StreamrConsumer task = new StreamrConsumer(host, port, args[2], l, (EventDispatcher) this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} else {
|
||||
l.log("streamrclient <host> <port> <destination>");
|
||||
l.log(" creates a tunnel that receives streaming data.");
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streamr server
|
||||
*
|
||||
* @param args {port, privkeyfile}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runStreamrServer(String args[], Logging l) {
|
||||
if (args.length == 2) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
File privKeyFile = new File(args[1]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
StreamrProducer task = new StreamrProducer(port, privKeyFile, args[1], l, (EventDispatcher) this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} else {
|
||||
l.log("streamrserver <port> <privkeyfile>");
|
||||
l.log(" creates a tunnel that sends streaming data.");
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the i2cp host and port
|
||||
*
|
||||
|
@ -124,8 +124,17 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", ex);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("OOM in HTTP server", oom);
|
||||
}
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
|
@ -83,9 +83,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " in");
|
||||
in.start();
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " out");
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
|
@ -0,0 +1,184 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple extension to the I2PTunnelServer that filters the registration
|
||||
* sequence to pass the destination hash of the client through as the hostname,
|
||||
* so an IRC Server may track users across nick changes.
|
||||
*
|
||||
* Of course, this requires the ircd actually use the hostname sent by
|
||||
* the client rather than the IP. It is common for ircds to ignore the
|
||||
* hostname in the USER message (unless it's coming from another server)
|
||||
* since it is easily spoofed. So you have to fix or, if you are lucky,
|
||||
* configure your ircd first. At least in unrealircd and ngircd this is
|
||||
* not configurable.
|
||||
*
|
||||
* There are three options for mangling the desthash. Put the option in the
|
||||
* "custom options" section of i2ptunnel.
|
||||
* - ircserver.cloakKey unset: Cloak with a random value that is persistent for
|
||||
* the life of this tunnel. This is the default.
|
||||
* - ircserver.cloakKey=none: Don't cloak. Users may be correlated with their
|
||||
* (probably) shared clients destination.
|
||||
* Of course if the ircd does cloaking than this is ok.
|
||||
* - ircserver.cloakKey=somepassphrase: Cloak with the hash of the passphrase. Use this to
|
||||
* have consistent mangling across restarts, or to
|
||||
* have multiple IRC servers cloak consistently to
|
||||
* be able to track users even when they switch servers.
|
||||
* Note: don't quote or put spaces in the passphrase,
|
||||
* the i2ptunnel gui can't handle it.
|
||||
*
|
||||
* There is no outbound filtering.
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelIRCServer.class);
|
||||
private static final String PROP_CLOAK="ircserver.cloakKey";
|
||||
private boolean _cloak;
|
||||
private byte[] _cloakKey; // 32 bytes of stuff to scramble the dest with
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
|
||||
public I2PTunnelIRCServer(InetAddress host, int port, File privkey, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
initCloak(tunnel);
|
||||
}
|
||||
|
||||
/** generate a random 32 bytes, or the hash of the passphrase */
|
||||
private void initCloak(I2PTunnel tunnel) {
|
||||
Properties opts = tunnel.getClientOptions();
|
||||
String passphrase = opts.getProperty(PROP_CLOAK);
|
||||
_cloak = passphrase == null || !"none".equals(passphrase);
|
||||
if (_cloak) {
|
||||
if (passphrase == null) {
|
||||
_cloakKey = new byte[Hash.HASH_LENGTH];
|
||||
tunnel.getContext().random().nextBytes(_cloakKey);
|
||||
} else {
|
||||
_cloakKey = SHA256Generator.getInstance().calculateHash(passphrase.trim().getBytes()).getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
try {
|
||||
// give them 15 seconds to send in the request
|
||||
socket.setReadTimeout(15*1000);
|
||||
InputStream in = socket.getInputStream();
|
||||
String modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("OOM in IRC server", oom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (Optionally) append 32 bytes of crap to the destination then return
|
||||
* the first few characters of the hash of the whole thing, + ".i2p".
|
||||
* Or do we want the full hash if the ircd is going to use this for
|
||||
* nickserv auto-login? Or even Base32 if it will be used in a
|
||||
* case-insensitive manner?
|
||||
*
|
||||
*/
|
||||
String cloakDest(Destination d) {
|
||||
Hash h;
|
||||
if (_cloak) {
|
||||
byte[] b = new byte[d.size() + _cloakKey.length];
|
||||
System.arraycopy(b, 0, d.toByteArray(), 0, d.size());
|
||||
System.arraycopy(b, d.size(), _cloakKey, 0, _cloakKey.length);
|
||||
h = SHA256Generator.getInstance().calculateHash(b);
|
||||
} else {
|
||||
h = d.calculateHash();
|
||||
}
|
||||
return h.toBase64().substring(0, 8) + ".i2p";
|
||||
}
|
||||
|
||||
/** keep reading until we see USER or SERVER */
|
||||
private String filterRegistration(InputStream in, String newHostname) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
int lineCount = 0;
|
||||
|
||||
while (true) {
|
||||
String s = DataHelper.readLine(in);
|
||||
if (s == null)
|
||||
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
if (++lineCount > 10)
|
||||
throw new IOException("Too many lines before USER or SERVER, giving up");
|
||||
s = s.trim();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got line: " + s);
|
||||
|
||||
String field[]=s.split(" ",5);
|
||||
String command;
|
||||
int idx=0;
|
||||
|
||||
if(field[0].charAt(0)==':')
|
||||
idx++;
|
||||
|
||||
try { command = field[idx++]; }
|
||||
catch (IndexOutOfBoundsException ioobe) // wtf, server sent borked command?
|
||||
{
|
||||
throw new IOException("Dropping defective message: index out of bounds while extracting command.");
|
||||
}
|
||||
|
||||
if ("USER".equalsIgnoreCase(command)) {
|
||||
if (field.length < idx + 4)
|
||||
throw new IOException("Too few parameters in USER message: " + s);
|
||||
// USER zzz1 hostname localhost :zzz
|
||||
// =>
|
||||
// USER zzz1 abcd1234.i2p localhost :zzz
|
||||
// this whole class is for these two lines...
|
||||
buf.append("USER ").append(field[idx]).append(' ').append(newHostname).append(".i2p ");
|
||||
buf.append(field[idx+2]).append(' ').append(field[idx+3]).append("\r\n");
|
||||
break;
|
||||
}
|
||||
buf.append(s).append("\r\n");
|
||||
if ("SERVER".equalsIgnoreCase(command))
|
||||
break;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("All done, sending: " + buf.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ public class TunnelController implements Logging {
|
||||
setConfig(config, prefix);
|
||||
_messages = new ArrayList(4);
|
||||
_running = false;
|
||||
if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
|
||||
if (createKey && getType().endsWith("server"))
|
||||
createPrivateKey();
|
||||
_starting = getStartOnLoad();
|
||||
}
|
||||
@ -134,6 +134,8 @@ public class TunnelController implements Logging {
|
||||
_log.warn("Cannot start the tunnel - no type specified");
|
||||
return;
|
||||
}
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
if ("httpclient".equals(type)) {
|
||||
startHttpClient();
|
||||
} else if("ircclient".equals(type)) {
|
||||
@ -144,19 +146,26 @@ public class TunnelController implements Logging {
|
||||
startConnectClient();
|
||||
} else if ("client".equals(type)) {
|
||||
startClient();
|
||||
} else if ("streamrclient".equals(type)) {
|
||||
startStreamrClient();
|
||||
} else if ("server".equals(type)) {
|
||||
startServer();
|
||||
} else if ("httpserver".equals(type)) {
|
||||
startHttpServer();
|
||||
} else if ("ircserver".equals(type)) {
|
||||
startIrcServer();
|
||||
} else if ("streamrserver".equals(type)) {
|
||||
startStreamrServer();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot start tunnel - unknown type [" + type + "]");
|
||||
return;
|
||||
}
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startHttpClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String proxyList = getProxyList();
|
||||
@ -165,13 +174,9 @@ public class TunnelController implements Logging {
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
|
||||
else
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startConnectClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String proxyList = getProxyList();
|
||||
@ -180,31 +185,46 @@ public class TunnelController implements Logging {
|
||||
_tunnel.runConnectClient(new String[] { listenPort, sharedClient }, this);
|
||||
else
|
||||
_tunnel.runConnectClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startIrcClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String dest = getTargetDestination();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startSocksClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Streamr client is a UDP server, use the listenPort field for targetPort
|
||||
* and the listenOnInterface field for the targetHost
|
||||
*/
|
||||
private void startStreamrClient() {
|
||||
String targetHost = getListenOnInterface();
|
||||
String targetPort = getListenPort();
|
||||
String dest = getTargetDestination();
|
||||
_tunnel.runStreamrClient(new String[] { targetHost, targetPort, dest }, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streamr server is a UDP client, use the targetPort field for listenPort
|
||||
* and the targetHost field for the listenOnInterface
|
||||
*/
|
||||
private void startStreamrServer() {
|
||||
String listenOn = getTargetHost();
|
||||
if ( (listenOn != null) && (listenOn.length() > 0) ) {
|
||||
_tunnel.runListenOn(new String[] { listenOn }, this);
|
||||
}
|
||||
String listenPort = getTargetPort();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runStreamrServer(new String[] { listenPort, privKeyFile }, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,38 +260,33 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
|
||||
private void startClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String dest = getTargetDestination();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startServer() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
String targetHost = getTargetHost();
|
||||
String targetPort = getTargetPort();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runServer(new String[] { targetHost, targetPort, privKeyFile }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startHttpServer() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
String targetHost = getTargetHost();
|
||||
String targetPort = getTargetPort();
|
||||
String spoofedHost = getSpoofedHost();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startIrcServer() {
|
||||
String targetHost = getTargetHost();
|
||||
String targetPort = getTargetPort();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runIrcServer(new String[] { targetHost, targetPort, privKeyFile }, this);
|
||||
}
|
||||
|
||||
private void setListenOn() {
|
||||
|
@ -0,0 +1,35 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Sends to one of many Sinks
|
||||
* @author zzz modded from streamr/MultiSource
|
||||
*/
|
||||
public class MultiSink implements Source, Sink {
|
||||
private static final Log _log = new Log(MultiSink.class);
|
||||
|
||||
public MultiSink(Map cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/** Don't use this - put sinks in the cache */
|
||||
public void setSink(Sink sink) {}
|
||||
|
||||
public void start() {}
|
||||
|
||||
public void send(Destination from, byte[] data) {
|
||||
Sink s = this.cache.get(from);
|
||||
if (s == null) {
|
||||
_log.error("No where to go for " + from.calculateHash().toBase64().substring(0, 6));
|
||||
return;
|
||||
}
|
||||
s.send(from, data);
|
||||
}
|
||||
|
||||
private Map<Destination, Sink> cache;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Track who the reply goes to
|
||||
* @author zzz
|
||||
*/
|
||||
public class ReplyTracker implements Source, Sink {
|
||||
private static final Log _log = new Log(MultiSink.class);
|
||||
|
||||
public ReplyTracker(Sink reply, Map cache) {
|
||||
this.reply = reply;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {}
|
||||
|
||||
public void send(Destination to, byte[] data) {
|
||||
this.cache.put(to, this.reply);
|
||||
this.sink.send(to, data);
|
||||
}
|
||||
|
||||
private Sink reply;
|
||||
private Map<Destination, Sink> cache;
|
||||
private Sink sink;
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.HexDump;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/*
|
||||
* Class that manages SOCKS 4/4a connections, and forwards them to
|
||||
* destination hosts or (eventually) some outproxy.
|
||||
*
|
||||
* @author zzz modded from SOCKS5Server
|
||||
*/
|
||||
public class SOCKS4aServer extends SOCKSServer {
|
||||
private static final Log _log = new Log(SOCKS4aServer.class);
|
||||
|
||||
private Socket clientSock = null;
|
||||
private boolean setupCompleted = false;
|
||||
|
||||
/**
|
||||
* Create a SOCKS4a server that communicates with the client using
|
||||
* the specified socket. This method should not be invoked
|
||||
* directly: new SOCKS4aServer objects should be created by using
|
||||
* SOCKSServerFactory.createSOCSKServer(). It is assumed that the
|
||||
* SOCKS VER field has been stripped from the input stream of the
|
||||
* client socket.
|
||||
*
|
||||
* @param clientSock client socket
|
||||
*/
|
||||
public SOCKS4aServer(Socket clientSock) {
|
||||
this.clientSock = clientSock;
|
||||
}
|
||||
|
||||
public Socket getClientSocket() throws SOCKSException {
|
||||
setupServer();
|
||||
|
||||
return clientSock;
|
||||
}
|
||||
|
||||
protected void setupServer() throws SOCKSException {
|
||||
if (setupCompleted) { return; }
|
||||
|
||||
DataInputStream in;
|
||||
DataOutputStream out;
|
||||
try {
|
||||
in = new DataInputStream(clientSock.getInputStream());
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
manageRequest(in, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
|
||||
setupCompleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* SOCKS4a request management. This method assumes that all the
|
||||
* stuff preceding or enveloping the actual request
|
||||
* has been stripped out of the input/output streams.
|
||||
*/
|
||||
private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
|
||||
int command = in.readByte() & 0xff;
|
||||
switch (command) {
|
||||
case Command.CONNECT:
|
||||
break;
|
||||
case Command.BIND:
|
||||
_log.debug("BIND command is not supported!");
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
throw new SOCKSException("BIND command not supported");
|
||||
default:
|
||||
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
throw new SOCKSException("Invalid command in request");
|
||||
}
|
||||
|
||||
connPort = in.readUnsignedShort();
|
||||
if (connPort == 0) {
|
||||
_log.debug("trying to connect to TCP port 0? Dropping!");
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
throw new SOCKSException("Invalid port number in request");
|
||||
}
|
||||
|
||||
connHostName = new String("");
|
||||
boolean alreadyWarned = false;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int octet = in.readByte() & 0xff;
|
||||
connHostName += Integer.toString(octet);
|
||||
if (i != 3) {
|
||||
connHostName += ".";
|
||||
if (octet != 0 && !alreadyWarned) {
|
||||
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
|
||||
alreadyWarned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// discard user name
|
||||
readString(in);
|
||||
|
||||
// SOCKS 4a
|
||||
if (connHostName.startsWith("0.0.0.") && !connHostName.equals("0.0.0.0"))
|
||||
connHostName = readString(in);
|
||||
}
|
||||
|
||||
private String readString(DataInputStream in) throws IOException {
|
||||
StringBuffer sb = new StringBuffer(16);
|
||||
char c;
|
||||
while ((c = (char) (in.readByte() & 0xff)) != 0)
|
||||
sb.append(c);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected void confirmConnection() throws SOCKSException {
|
||||
DataInputStream in;
|
||||
DataOutputStream out;
|
||||
try {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
sendRequestReply(Reply.SUCCEEDED, InetAddress.getByName("127.0.0.1"), 1, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified reply to a request of the client. Either
|
||||
* one of inetAddr or domainName can be null, depending on
|
||||
* addressType.
|
||||
*/
|
||||
private void sendRequestReply(int replyCode, InetAddress inetAddr,
|
||||
int bindPort, DataOutputStream out) throws IOException {
|
||||
ByteArrayOutputStream reps = new ByteArrayOutputStream();
|
||||
DataOutputStream dreps = new DataOutputStream(reps);
|
||||
|
||||
// Reserved byte, should be 0x00
|
||||
dreps.write(0x00);
|
||||
dreps.write(replyCode);
|
||||
dreps.writeShort(bindPort);
|
||||
dreps.write(inetAddr.getAddress());
|
||||
|
||||
byte[] reply = reps.toByteArray();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Sending request reply:\n" + HexDump.dump(reply));
|
||||
}
|
||||
|
||||
out.write(reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an I2PSocket that can be used to send/receive 8-bit clean data
|
||||
* to/from the destination of the SOCKS connection.
|
||||
*
|
||||
* @return an I2PSocket connected with the destination
|
||||
*/
|
||||
public I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException {
|
||||
setupServer();
|
||||
|
||||
if (connHostName == null) {
|
||||
_log.error("BUG: destination host name has not been initialized!");
|
||||
throw new SOCKSException("BUG! See the logs!");
|
||||
}
|
||||
if (connPort == 0) {
|
||||
_log.error("BUG: destination port has not been initialized!");
|
||||
throw new SOCKSException("BUG! See the logs!");
|
||||
}
|
||||
|
||||
DataOutputStream out; // for errors
|
||||
try {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
|
||||
// FIXME: here we should read our config file, select an
|
||||
// outproxy, and instantiate the proper socket class that
|
||||
// handles the outproxy itself (SOCKS4a, SOCKS4a, HTTP CONNECT...).
|
||||
I2PSocket destSock;
|
||||
|
||||
try {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_log.debug("connecting to " + connHostName + "...");
|
||||
// Let's not due a new Dest for every request, huh?
|
||||
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
|
||||
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
|
||||
String err = "No localhost accesses allowed through the Socks Proxy";
|
||||
_log.error(err);
|
||||
try {
|
||||
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)
|
||||
// use eepProxy configured outproxies?
|
||||
String err = "No handler for HTTP outproxy implemented - to: " + connHostName;
|
||||
_log.error(err);
|
||||
try {
|
||||
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.size() <= 0) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either";
|
||||
_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 = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
|
||||
String proxy = proxies.get(p);
|
||||
_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(I2PTunnel.destFromName(proxy));
|
||||
}
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} catch (DataFormatException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error in destination format");
|
||||
} catch (SocketException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (I2PException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some namespaces to enclose SOCKS protocol codes
|
||||
*/
|
||||
private static class Command {
|
||||
private static final int CONNECT = 0x01;
|
||||
private static final int BIND = 0x02;
|
||||
}
|
||||
|
||||
private static class Reply {
|
||||
private static final int SUCCEEDED = 0x5a;
|
||||
private static final int CONNECTION_REFUSED = 0x5b;
|
||||
}
|
||||
}
|
@ -13,12 +13,15 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.HexDump;
|
||||
import net.i2p.util.Log;
|
||||
@ -67,7 +70,8 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
init(in, out);
|
||||
manageRequest(in, out);
|
||||
if (manageRequest(in, out) == Command.UDP_ASSOCIATE)
|
||||
handleUDP(in, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
@ -111,7 +115,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
* initialization, integrity/confidentiality encapsulations, etc)
|
||||
* has been stripped out of the input/output streams.
|
||||
*/
|
||||
private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
private int manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
int socksVer = in.readByte() & 0xff;
|
||||
if (socksVer != SOCKS_VERSION_5) {
|
||||
_log.debug("error in SOCKS5 request (protocol != 5? wtf?)");
|
||||
@ -127,9 +131,12 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
throw new SOCKSException("BIND command not supported");
|
||||
case Command.UDP_ASSOCIATE:
|
||||
/*** if(!Boolean.valueOf(tunnel.getOptions().getProperty("i2ptunnel.socks.allowUDP")).booleanValue()) {
|
||||
_log.debug("UDP ASSOCIATE command is not supported!");
|
||||
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
throw new SOCKSException("UDP ASSOCIATE command not supported");
|
||||
***/
|
||||
break;
|
||||
default:
|
||||
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
|
||||
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
@ -152,7 +159,8 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
connHostName += ".";
|
||||
}
|
||||
}
|
||||
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
|
||||
if (command != Command.UDP_ASSOCIATE)
|
||||
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
|
||||
break;
|
||||
case AddressType.DOMAINNAME:
|
||||
{
|
||||
@ -168,9 +176,12 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
_log.debug("DOMAINNAME address type in request: " + connHostName);
|
||||
break;
|
||||
case AddressType.IPV6:
|
||||
_log.warn("IP V6 address type in request! Is your client secure?" + " (IPv6 is not supported, anyway :-)");
|
||||
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
throw new SOCKSException("IPV6 addresses not supported");
|
||||
if (command != Command.UDP_ASSOCIATE) {
|
||||
_log.warn("IP V6 address type in request! Is your client secure?" + " (IPv6 is not supported, anyway :-)");
|
||||
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
throw new SOCKSException("IPV6 addresses not supported");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_log.debug("unknown address type in request (" + Integer.toHexString(command) + ")");
|
||||
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
@ -183,6 +194,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
throw new SOCKSException("Invalid port number in request");
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
protected void confirmConnection() throws SOCKSException {
|
||||
@ -293,6 +305,13 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
// Let's not due a new Dest for every request, huh?
|
||||
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
Destination dest = I2PTunnel.destFromName(connHostName);
|
||||
if (dest == null) {
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Host not found");
|
||||
}
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
|
||||
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
|
||||
String err = "No localhost accesses allowed through the Socks Proxy";
|
||||
@ -358,6 +377,59 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
return destSock;
|
||||
}
|
||||
|
||||
// This isn't really the right place for this, we can't stop the tunnel once it starts.
|
||||
static SOCKSUDPTunnel _tunnel;
|
||||
static Object _startLock = new Object();
|
||||
static byte[] dummyIP = new byte[4];
|
||||
/**
|
||||
* We got a UDP associate command.
|
||||
* Loop here looking for more, never return normally,
|
||||
* or else I2PSocksTunnel will create a streaming lib connection.
|
||||
*
|
||||
* Do UDP Socks clients actually send more than one Associate request?
|
||||
* RFC 1928 isn't clear... maybe not.
|
||||
*/
|
||||
private void handleUDP(DataInputStream in, DataOutputStream out) throws SOCKSException {
|
||||
List<Integer> ports = new ArrayList(1);
|
||||
synchronized (_startLock) {
|
||||
if (_tunnel == null) {
|
||||
// tunnel options?
|
||||
_tunnel = new SOCKSUDPTunnel(new I2PTunnel());
|
||||
_tunnel.startRunning();
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
// Set it up. connHostName and connPort are the client's info.
|
||||
InetAddress ia = null;
|
||||
try {
|
||||
ia = InetAddress.getByAddress(connHostName, dummyIP);
|
||||
} catch (UnknownHostException uhe) {} // won't happen, no resolving done here
|
||||
int myPort = _tunnel.add(ia, connPort);
|
||||
ports.add(Integer.valueOf(myPort));
|
||||
try {
|
||||
sendRequestReply(Reply.SUCCEEDED, AddressType.IPV4, InetAddress.getByName("127.0.0.1"), null, myPort, out);
|
||||
} catch (IOException ioe) { break; }
|
||||
|
||||
// wait for more ???
|
||||
try {
|
||||
int command = manageRequest(in, out);
|
||||
// don't do this...
|
||||
if (command != Command.UDP_ASSOCIATE)
|
||||
break;
|
||||
} catch (IOException ioe) { break; }
|
||||
catch (SOCKSException ioe) { break; }
|
||||
}
|
||||
|
||||
for (Integer i : ports)
|
||||
_tunnel.remove(i);
|
||||
|
||||
// Prevent I2PSocksTunnel from calling getDestinationI2PSocket() above
|
||||
// to create a streaming lib connection...
|
||||
// This isn't very elegant...
|
||||
//
|
||||
throw new SOCKSException("End of UDP Processing");
|
||||
}
|
||||
|
||||
/*
|
||||
* Some namespaces to enclose SOCKS protocol codes
|
||||
*/
|
||||
|
@ -0,0 +1,89 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
|
||||
/**
|
||||
* Save the SOCKS header from a datagram
|
||||
* Ref: RFC 1928
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSHeader {
|
||||
|
||||
/**
|
||||
* @param data the whole packet
|
||||
*/
|
||||
public SOCKSHeader(byte[] data) {
|
||||
if (data.length <= 8)
|
||||
throw new IllegalArgumentException("Header too short: " + data.length);
|
||||
if (data[0] != 0 || data[1] != 0)
|
||||
throw new IllegalArgumentException("Not a SOCKS datagram?");
|
||||
if (data[2] != 0)
|
||||
throw new IllegalArgumentException("We can't handle fragments!");
|
||||
int headerlen = 0;
|
||||
int addressType = data[3];
|
||||
if (addressType == 1) {
|
||||
// this will fail in getDestination()
|
||||
headerlen = 6 + 4;
|
||||
} else if (addressType == 3) {
|
||||
headerlen = 6 + 1 + (data[4] & 0xff);
|
||||
} else if (addressType == 4) {
|
||||
// this will fail in getDestination()
|
||||
// but future garlicat partial hash lookup possible?
|
||||
headerlen = 6 + 16;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown address type: " + addressType);
|
||||
}
|
||||
if (data.length < headerlen)
|
||||
throw new IllegalArgumentException("Header too short: " + data.length);
|
||||
|
||||
this.header = new byte[headerlen];
|
||||
System.arraycopy(this.header, 0, data, 0, headerlen);
|
||||
}
|
||||
|
||||
private static final byte[] beg = {0,0,0,3,60};
|
||||
private static final byte[] end = {'.','b','3','2','.','i','2','p',0,0};
|
||||
|
||||
/**
|
||||
* Make a dummy header from a dest,
|
||||
* for those cases where we want to receive unsolicited datagrams.
|
||||
* Unused for now.
|
||||
*/
|
||||
public SOCKSHeader(Destination dest) {
|
||||
this.header = new byte[beg.length + 52 + end.length];
|
||||
System.arraycopy(this.header, 0, beg, 0, beg.length);
|
||||
String b32 = Base32.encode(dest.calculateHash().getData());
|
||||
System.arraycopy(this.header, beg.length, b32.getBytes(), 0, 52);
|
||||
System.arraycopy(this.header, beg.length + 52, end, 0, end.length);
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
int addressType = this.header[3];
|
||||
if (addressType != 3)
|
||||
return null;
|
||||
int namelen = (this.header[4] & 0xff);
|
||||
byte[] nameBytes = new byte[namelen];
|
||||
System.arraycopy(nameBytes, 0, this.header, 5, namelen);
|
||||
return new String(nameBytes);
|
||||
}
|
||||
|
||||
public Destination getDestination() {
|
||||
String name = getHost();
|
||||
if (name == null)
|
||||
return null;
|
||||
try {
|
||||
// the naming service does caching (thankfully)
|
||||
return I2PTunnel.destFromName(name);
|
||||
} catch (DataFormatException dfe) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return header;
|
||||
}
|
||||
|
||||
private byte[] header;
|
||||
}
|
@ -44,6 +44,10 @@ public class SOCKSServerFactory {
|
||||
int socksVer = in.readByte();
|
||||
|
||||
switch (socksVer) {
|
||||
case 0x04:
|
||||
// SOCKS version 4/4a
|
||||
serv = new SOCKS4aServer(s);
|
||||
break;
|
||||
case 0x05:
|
||||
// SOCKS version 5
|
||||
serv = new SOCKS5Server(s);
|
||||
|
@ -0,0 +1,77 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
|
||||
/**
|
||||
* Implements a UDP port and Socks encapsulation / decapsulation.
|
||||
* This is for a single port. If there is demuxing for multiple
|
||||
* ports, it happens outside of here.
|
||||
*
|
||||
* TX:
|
||||
* UDPSource -> SOCKSUDPUnwrapper -> ReplyTracker ( -> I2PSink in SOCKSUDPTunnel)
|
||||
*
|
||||
* RX:
|
||||
* UDPSink <- SOCKSUDPWrapper ( <- MultiSink <- I2PSource in SOCKSUDPTunnel)
|
||||
*
|
||||
* The Unwrapper passes headers to the Wrapper through a cache.
|
||||
* The ReplyTracker passes sinks to MultiSink through a cache.
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSUDPPort implements Source, Sink {
|
||||
|
||||
public SOCKSUDPPort(InetAddress host, int port, Map replyMap) {
|
||||
|
||||
// this passes the host and port from UDPUnwrapper to UDPWrapper
|
||||
Map cache = new ConcurrentHashMap(4);
|
||||
|
||||
// rcv from I2P and send to a port
|
||||
this.wrapper = new SOCKSUDPWrapper(cache);
|
||||
this.udpsink = new UDPSink(host, port);
|
||||
this.wrapper.setSink(this.udpsink);
|
||||
|
||||
// rcv from the same port and send to I2P
|
||||
DatagramSocket sock = this.udpsink.getSocket();
|
||||
this.udpsource = new UDPSource(sock);
|
||||
this.unwrapper = new SOCKSUDPUnwrapper(cache);
|
||||
this.udpsource.setSink(this.unwrapper);
|
||||
this.udptracker = new ReplyTracker(this, replyMap);
|
||||
this.unwrapper.setSink(this.udptracker);
|
||||
}
|
||||
|
||||
/** Socks passes this back to the client on the TCP connection */
|
||||
public int getPort() {
|
||||
return this.udpsink.getPort();
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.udptracker.setSink(sink);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
// the other Sources don't use start
|
||||
this.udpsource.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.udpsink.stop();
|
||||
this.udpsource.stop();
|
||||
}
|
||||
|
||||
public void send(Destination from, byte[] data) {
|
||||
this.wrapper.send(from, data);
|
||||
}
|
||||
|
||||
|
||||
private UDPSink udpsink;
|
||||
private UDPSource udpsource;
|
||||
private SOCKSUDPWrapper wrapper;
|
||||
private SOCKSUDPUnwrapper unwrapper;
|
||||
private ReplyTracker udptracker;
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
|
||||
/**
|
||||
* A Datagram Tunnel that can have multiple bidirectional ports on the UDP side.
|
||||
*
|
||||
* TX:
|
||||
* (ReplyTracker in multiple SOCKSUDPPorts -> ) I2PSink
|
||||
*
|
||||
* RX:
|
||||
* (SOCKSUDPWrapper in multiple SOCKSUDPPorts <- ) MultiSink <- I2PSource
|
||||
*
|
||||
* The reply from a dest goes to the last SOCKSUDPPort that sent to that dest.
|
||||
* If multiple ports are talking to a dest at the same time, this isn't
|
||||
* going to work very well.
|
||||
*
|
||||
* @author zzz modded from streamr/StreamrConsumer
|
||||
*/
|
||||
public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
|
||||
|
||||
/**
|
||||
* Set up a tunnel with no UDP side yet.
|
||||
* Use add() for each port.
|
||||
*/
|
||||
public SOCKSUDPTunnel(I2PTunnel tunnel) {
|
||||
super(null, tunnel, tunnel, tunnel);
|
||||
|
||||
this.ports = new ConcurrentHashMap(1);
|
||||
this.cache = new ConcurrentHashMap(1);
|
||||
this.demuxer = new MultiSink(this.cache);
|
||||
setSink(this.demuxer);
|
||||
}
|
||||
|
||||
|
||||
/** @return the UDP port number */
|
||||
public int add(InetAddress host, int port) {
|
||||
SOCKSUDPPort sup = new SOCKSUDPPort(host, port, this.cache);
|
||||
this.ports.put(Integer.valueOf(sup.getPort()), sup);
|
||||
sup.setSink(this);
|
||||
sup.start();
|
||||
return sup.getPort();
|
||||
}
|
||||
|
||||
public void remove(Integer port) {
|
||||
SOCKSUDPPort sup = this.ports.remove(port);
|
||||
if (sup != null)
|
||||
sup.stop();
|
||||
for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();) {
|
||||
Map.Entry<Destination, SOCKSUDPPort> e = (Map.Entry) iter.next();
|
||||
if (e.getValue() == sup)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public final void startRunning() {
|
||||
super.startRunning();
|
||||
// demuxer start() doesn't do anything
|
||||
startall();
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
stopall();
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
/** you should really add() after startRunning() */
|
||||
private void startall() {
|
||||
}
|
||||
|
||||
private void stopall() {
|
||||
for (SOCKSUDPPort sup : this.ports.values()) {
|
||||
sup.stop();
|
||||
}
|
||||
this.ports.clear();
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Map<Integer, SOCKSUDPPort> ports;
|
||||
private Map<Destination, SOCKSUDPPort> cache;
|
||||
private MultiSink demuxer;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Strip a SOCKS header off a datagram, convert it to a Destination
|
||||
* Ref: RFC 1928
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSUDPUnwrapper implements Source, Sink {
|
||||
private static final Log _log = new Log(SOCKSUDPUnwrapper.class);
|
||||
|
||||
/**
|
||||
* @param cache put headers here to pass to SOCKSUDPWrapper
|
||||
*/
|
||||
public SOCKSUDPUnwrapper(Map<Destination, SOCKSHeader> cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void send(Destination ignored_from, byte[] data) {
|
||||
SOCKSHeader h;
|
||||
try {
|
||||
h = new SOCKSHeader(data);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(iae.toString());
|
||||
return;
|
||||
}
|
||||
Destination dest = h.getDestination();
|
||||
if (dest == null) {
|
||||
// no, we aren't going to send non-i2p traffic to a UDP outproxy :)
|
||||
_log.error("Destination not found: " + h.getHost());
|
||||
return;
|
||||
}
|
||||
|
||||
cache.put(dest, h);
|
||||
|
||||
int headerlen = h.getBytes().length;
|
||||
byte unwrapped[] = new byte[data.length - headerlen];
|
||||
System.arraycopy(unwrapped, 0, data, headerlen, unwrapped.length);
|
||||
this.sink.send(dest, unwrapped);
|
||||
}
|
||||
|
||||
private Sink sink;
|
||||
private Map<Destination, SOCKSHeader> cache;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
|
||||
/**
|
||||
* Put a SOCKS header on a datagram
|
||||
* Ref: RFC 1928
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSUDPWrapper implements Source, Sink {
|
||||
public SOCKSUDPWrapper(Map<Destination, SOCKSHeader> cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
* Use the cached header, which should have the host string and port
|
||||
*
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
if (this.sink == null)
|
||||
return;
|
||||
|
||||
SOCKSHeader h = cache.get(from);
|
||||
if (h == null) {
|
||||
// RFC 1928 says drop
|
||||
// h = new SOCKSHeader(from);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] header = h.getBytes();
|
||||
byte wrapped[] = new byte[header.length + data.length];
|
||||
System.arraycopy(wrapped, 0, header, 0, header.length);
|
||||
System.arraycopy(wrapped, header.length, data, 0, data.length);
|
||||
this.sink.send(from, wrapped);
|
||||
}
|
||||
|
||||
private Sink sink;
|
||||
private Map<Destination, SOCKSHeader> cache;
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
|
||||
/**
|
||||
* Sends to many Sinks
|
||||
* @author welterde
|
||||
* @author zzz modded for I2PTunnel
|
||||
*/
|
||||
public class MultiSource implements Source, Sink {
|
||||
public MultiSource() {
|
||||
this.sinks = new CopyOnWriteArrayList<Destination>();
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {}
|
||||
|
||||
public void stop() {
|
||||
this.sinks.clear();
|
||||
}
|
||||
|
||||
public void send(Destination ignored_from, byte[] data) {
|
||||
for(Destination dest : this.sinks) {
|
||||
this.sink.send(dest, data);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Destination sink) {
|
||||
this.sinks.add(sink);
|
||||
}
|
||||
|
||||
public void remove(Destination sink) {
|
||||
this.sinks.remove(sink);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private Sink sink;
|
||||
private List<Destination> sinks;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde/zzz
|
||||
*/
|
||||
public class Pinger implements Source, Runnable {
|
||||
public Pinger() {
|
||||
this.thread = new Thread(this);
|
||||
}
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.running = true;
|
||||
this.waitlock = new Object();
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.running = false;
|
||||
synchronized(this.waitlock) {
|
||||
this.waitlock.notifyAll();
|
||||
}
|
||||
// send unsubscribe-message
|
||||
byte[] data = new byte[1];
|
||||
data[0] = 1;
|
||||
this.sink.send(null, data);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// send subscribe-message
|
||||
byte[] data = new byte[1];
|
||||
data[0] = 0;
|
||||
int i = 0;
|
||||
while(this.running) {
|
||||
//System.out.print("p");
|
||||
this.sink.send(null, data);
|
||||
synchronized(this.waitlock) {
|
||||
int delay = 10000;
|
||||
if (i < 5) {
|
||||
i++;
|
||||
delay = 2000;
|
||||
}
|
||||
try {
|
||||
this.waitlock.wait(delay);
|
||||
} catch(InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Sink sink;
|
||||
protected Thread thread;
|
||||
protected Object waitlock;
|
||||
protected boolean running;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
|
||||
/**
|
||||
* Compared to a standard I2PTunnel,
|
||||
* this acts like a client on the I2P side (no privkey file)
|
||||
* but a server on the UDP side (sends to a configured host/port)
|
||||
*
|
||||
* @author welterde
|
||||
* @author zzz modded for I2PTunnel
|
||||
*/
|
||||
public class StreamrConsumer extends I2PTunnelUDPClientBase {
|
||||
|
||||
public StreamrConsumer(InetAddress host, int port, String destination,
|
||||
Logging l, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) {
|
||||
super(destination, l, notifyThis, tunnel);
|
||||
|
||||
// create udp-destination
|
||||
this.sink = new UDPSink(host, port);
|
||||
setSink(this.sink);
|
||||
|
||||
// create pinger
|
||||
this.pinger = new Pinger();
|
||||
this.pinger.setSink(this);
|
||||
}
|
||||
|
||||
public final void startRunning() {
|
||||
super.startRunning();
|
||||
// send subscribe-message
|
||||
this.pinger.start();
|
||||
l.log("Streamr client ready");
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
// send unsubscribe-message
|
||||
this.pinger.stop();
|
||||
this.sink.stop();
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private UDPSink sink;
|
||||
private Pinger pinger;
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
// system
|
||||
import java.io.File;
|
||||
|
||||
// i2p
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPServerBase;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
|
||||
/**
|
||||
* Compared to a standard I2PTunnel,
|
||||
* this acts like a server on the I2P side (persistent privkey file)
|
||||
* but a client on the UDP side (receives on a configured port)
|
||||
*
|
||||
* @author welterde
|
||||
* @author zzz modded for I2PTunnel
|
||||
*/
|
||||
public class StreamrProducer extends I2PTunnelUDPServerBase {
|
||||
|
||||
public StreamrProducer(int port,
|
||||
File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
// verify subscription requests
|
||||
super(true, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
|
||||
// The broadcaster
|
||||
this.multi = new MultiSource();
|
||||
this.multi.setSink(this);
|
||||
|
||||
// The listener
|
||||
this.subscriber = new Subscriber(this.multi);
|
||||
setSink(this.subscriber);
|
||||
|
||||
// now start udp-server
|
||||
this.server = new UDPSource(port);
|
||||
this.server.setSink(this.multi);
|
||||
}
|
||||
|
||||
public final void startRunning() {
|
||||
super.startRunning();
|
||||
this.server.start();
|
||||
l.log("Streamr server ready");
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
this.server.stop();
|
||||
this.multi.stop();
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private MultiSource multi;
|
||||
private UDPSource server;
|
||||
private Sink subscriber;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
// system
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
// i2p
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPServerBase;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
/**
|
||||
* server-mode
|
||||
* @author welterde
|
||||
* @author zzz modded from Producer for I2PTunnel
|
||||
*/
|
||||
public class Subscriber implements Sink {
|
||||
|
||||
public Subscriber(MultiSource multi) {
|
||||
this.multi = multi;
|
||||
// subscriptions
|
||||
this.subscriptions = new ConcurrentHashSet<Destination>();
|
||||
}
|
||||
|
||||
public void send(Destination dest, byte[] data) {
|
||||
if(dest == null || data.length < 1) {
|
||||
// invalid packet
|
||||
// TODO: write to log
|
||||
} else {
|
||||
byte ctrl = data[0];
|
||||
if(ctrl == 0) {
|
||||
if (!this.subscriptions.contains(dest)) {
|
||||
// subscribe
|
||||
System.out.println("Add subscription: " + dest.toBase64().substring(0,4));
|
||||
this.subscriptions.add(dest);
|
||||
this.multi.add(dest);
|
||||
} // else already subscribed
|
||||
} else if(ctrl == 1) {
|
||||
// unsubscribe
|
||||
System.out.println("Remove subscription: " + dest.toBase64().substring(0,4));
|
||||
boolean removed = this.subscriptions.remove(dest);
|
||||
if(removed)
|
||||
multi.remove(dest);
|
||||
} else {
|
||||
// invalid packet
|
||||
// TODO: write to log
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private I2PSession sess;
|
||||
private Source listener;
|
||||
private Set<Destination> subscriptions;
|
||||
private MultiSource multi;
|
||||
private Source server;
|
||||
}
|
70
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java
Normal file
70
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
// i2p
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
|
||||
/**
|
||||
* Producer
|
||||
*
|
||||
* This sends to a fixed destination specified in the constructor
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public class I2PSink implements Sink {
|
||||
public I2PSink(I2PSession sess, Destination dest) {
|
||||
this(sess, dest, false);
|
||||
}
|
||||
public I2PSink(I2PSession sess, Destination dest, boolean raw) {
|
||||
this.sess = sess;
|
||||
this.dest = dest;
|
||||
this.raw = raw;
|
||||
|
||||
// create maker
|
||||
if (!raw)
|
||||
this.maker = new I2PDatagramMaker(this.sess);
|
||||
}
|
||||
|
||||
/** @param src ignored */
|
||||
public synchronized void send(Destination src, byte[] data) {
|
||||
//System.out.print("w");
|
||||
// create payload
|
||||
byte[] payload;
|
||||
if(!this.raw)
|
||||
payload = this.maker.makeI2PDatagram(data);
|
||||
else
|
||||
payload = data;
|
||||
|
||||
// send message
|
||||
try {
|
||||
this.sess.sendMessage(this.dest, payload);
|
||||
} catch(I2PSessionException exc) {
|
||||
// TODO: handle better
|
||||
exc.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected boolean raw;
|
||||
protected I2PSession sess;
|
||||
protected Destination dest;
|
||||
protected I2PDatagramMaker maker;
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
// i2p
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
|
||||
/**
|
||||
* Producer
|
||||
*
|
||||
* This sends to any destination specified in send()
|
||||
*
|
||||
* @author zzz modded from I2PSink by welterde
|
||||
*/
|
||||
public class I2PSinkAnywhere implements Sink {
|
||||
public I2PSinkAnywhere(I2PSession sess) {
|
||||
this(sess, false);
|
||||
}
|
||||
public I2PSinkAnywhere(I2PSession sess, boolean raw) {
|
||||
this.sess = sess;
|
||||
this.raw = raw;
|
||||
|
||||
// create maker
|
||||
if (!raw)
|
||||
this.maker = new I2PDatagramMaker(this.sess);
|
||||
}
|
||||
|
||||
/** @param to - where it's going */
|
||||
public synchronized void send(Destination to, byte[] data) {
|
||||
// create payload
|
||||
byte[] payload;
|
||||
if(!this.raw)
|
||||
payload = this.maker.makeI2PDatagram(data);
|
||||
else
|
||||
payload = data;
|
||||
|
||||
// send message
|
||||
try {
|
||||
this.sess.sendMessage(to, payload);
|
||||
} catch(I2PSessionException exc) {
|
||||
// TODO: handle better
|
||||
exc.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected boolean raw;
|
||||
protected I2PSession sess;
|
||||
protected Destination dest;
|
||||
protected I2PDatagramMaker maker;
|
||||
}
|
123
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
Normal file
123
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
// system
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
// i2p
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public class I2PSource implements Source, Runnable {
|
||||
public I2PSource(I2PSession sess) {
|
||||
this(sess, true, false);
|
||||
}
|
||||
public I2PSource(I2PSession sess, boolean verify) {
|
||||
this(sess, verify, false);
|
||||
}
|
||||
public I2PSource(I2PSession sess, boolean verify, boolean raw) {
|
||||
this.sess = sess;
|
||||
this.sink = null;
|
||||
this.verify = verify;
|
||||
this.raw = raw;
|
||||
|
||||
// create queue
|
||||
this.queue = new ArrayBlockingQueue(256);
|
||||
|
||||
// create listener
|
||||
this.sess.setSessionListener(new Listener());
|
||||
|
||||
// create thread
|
||||
this.thread = new Thread(this);
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// create dissector
|
||||
I2PDatagramDissector diss = new I2PDatagramDissector();
|
||||
while(true) {
|
||||
try {
|
||||
// get id
|
||||
int id = this.queue.take();
|
||||
|
||||
// receive message
|
||||
byte[] msg = this.sess.receiveMessage(id);
|
||||
|
||||
if(!this.raw) {
|
||||
// load datagram into it
|
||||
diss.loadI2PDatagram(msg);
|
||||
|
||||
// now call sink
|
||||
if(this.verify)
|
||||
this.sink.send(diss.getSender(), diss.getPayload());
|
||||
else
|
||||
this.sink.send(diss.extractSender(), diss.extractPayload());
|
||||
} else {
|
||||
// verify is ignored
|
||||
this.sink.send(null, msg);
|
||||
}
|
||||
//System.out.print("r");
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected class Listener implements I2PSessionListener {
|
||||
|
||||
public void messageAvailable(I2PSession sess, int id, long size) {
|
||||
try {
|
||||
queue.put(id);
|
||||
} catch(Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession arg0, int arg1) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void disconnected(I2PSession arg0) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected I2PSession sess;
|
||||
protected BlockingQueue<Integer> queue;
|
||||
protected Sink sink;
|
||||
protected Thread thread;
|
||||
protected boolean verify;
|
||||
protected boolean raw;
|
||||
}
|
17
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java
Normal file
17
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
// i2p
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public interface Sink {
|
||||
public void send(Destination src, byte[] data);
|
||||
}
|
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java
Normal file
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public interface Source {
|
||||
public void setSink(Sink sink);
|
||||
public void start();
|
||||
}
|
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java
Normal file
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public interface Stream {
|
||||
public void start();
|
||||
public void stop();
|
||||
}
|
77
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java
Normal file
77
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
// system
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
|
||||
// i2p
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public class UDPSink implements Sink {
|
||||
public UDPSink(InetAddress host, int port) {
|
||||
// create socket
|
||||
try {
|
||||
this.sock = new DatagramSocket();
|
||||
} catch(Exception e) {
|
||||
// TODO: fail better
|
||||
throw new RuntimeException("failed to open udp-socket", e);
|
||||
}
|
||||
|
||||
this.remoteHost = host;
|
||||
|
||||
// remote port
|
||||
this.remotePort = port;
|
||||
}
|
||||
|
||||
public void send(Destination src, byte[] data) {
|
||||
// if data.length > this.sock.getSendBufferSize() ...
|
||||
|
||||
// create packet
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length, this.remoteHost, this.remotePort);
|
||||
|
||||
// send packet
|
||||
try {
|
||||
this.sock.send(packet);
|
||||
} catch(Exception e) {
|
||||
// TODO: fail a bit better
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.sock.getLocalPort();
|
||||
}
|
||||
|
||||
/** to pass to UDPSource constructor */
|
||||
public DatagramSocket getSocket() {
|
||||
return this.sock;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.sock.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected DatagramSocket sock;
|
||||
protected InetAddress remoteHost;
|
||||
protected int remotePort;
|
||||
|
||||
}
|
91
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
Normal file
91
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
// system
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramPacket;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public class UDPSource implements Source, Runnable {
|
||||
public static final int MAX_SIZE = 15360;
|
||||
public UDPSource(int port) {
|
||||
this.sink = null;
|
||||
|
||||
// create udp-socket
|
||||
try {
|
||||
this.sock = new DatagramSocket(port);
|
||||
} catch(Exception e) {
|
||||
throw new RuntimeException("failed to listen...", e);
|
||||
}
|
||||
|
||||
// create thread
|
||||
this.thread = new Thread(this);
|
||||
}
|
||||
|
||||
/** use socket from UDPSink */
|
||||
public UDPSource(DatagramSocket sock) {
|
||||
this.sink = null;
|
||||
this.sock = sock;
|
||||
this.thread = new Thread(this);
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// create packet
|
||||
byte[] buf = new byte[MAX_SIZE];
|
||||
DatagramPacket pack = new DatagramPacket(buf, buf.length);
|
||||
while(true) {
|
||||
try {
|
||||
// receive...
|
||||
this.sock.receive(pack);
|
||||
|
||||
// create new data array
|
||||
byte[] nbuf = new byte[pack.getLength()];
|
||||
|
||||
// copy over
|
||||
System.arraycopy(pack.getData(), 0, nbuf, 0, nbuf.length);
|
||||
|
||||
// transfer to sink
|
||||
this.sink.send(null, nbuf);
|
||||
//System.out.print("i");
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.sock.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected DatagramSocket sock;
|
||||
protected Sink sink;
|
||||
protected Thread thread;
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel.udpTunnel;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.I2PTunnelTask;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelUDPClientBase.class);
|
||||
protected I2PAppContext _context;
|
||||
protected Logging l;
|
||||
|
||||
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
|
||||
private static volatile long __clientId = 0;
|
||||
protected long _clientId;
|
||||
|
||||
protected Destination dest = null;
|
||||
|
||||
private boolean listenerReady = false;
|
||||
|
||||
private ServerSocket ss;
|
||||
|
||||
private Object startLock = new Object();
|
||||
private boolean startRunning = false;
|
||||
|
||||
private byte[] pubkey;
|
||||
|
||||
private String handlerName;
|
||||
|
||||
private Object conLock = new Object();
|
||||
|
||||
/** How many connections will we allow to be in the process of being built at once? */
|
||||
private int _numConnectionBuilders;
|
||||
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
|
||||
private int _maxWaitTime;
|
||||
|
||||
private I2PSession _session;
|
||||
private Source _i2pSource;
|
||||
private Sink _i2pSink;
|
||||
private Destination _otherDest;
|
||||
|
||||
/**
|
||||
* Base client class that sets up an I2P Datagram client destination.
|
||||
* The UDP side is not implemented here, as there are at least
|
||||
* two possibilities:
|
||||
*
|
||||
* 1) UDP side is a "server"
|
||||
* Example: Streamr Consumer
|
||||
* - Configure a destination host and port
|
||||
* - External application sends no data
|
||||
* - Extending class must have a constructor with host and port arguments
|
||||
*
|
||||
* 2) UDP side is a client/server
|
||||
* Example: SOCKS UDP (DNS requests?)
|
||||
* - configure an inbound port and a destination host and port
|
||||
* - External application sends and receives data
|
||||
* - Extending class must have a constructor with host and 2 port arguments
|
||||
*
|
||||
* So the implementing class must create a UDPSource and/or UDPSink,
|
||||
* and must call setSink().
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*
|
||||
* @author zzz with portions from welterde's streamr
|
||||
*/
|
||||
public I2PTunnelUDPClientBase(String destination, Logging l, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super("UDPServer", notifyThis, tunnel);
|
||||
_clientId = ++__clientId;
|
||||
this.l = l;
|
||||
|
||||
_context = tunnel.getContext();
|
||||
|
||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||
|
||||
// create i2pclient and destination
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination dest;
|
||||
byte[] key;
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
dest = client.createDestination(out);
|
||||
key = out.toByteArray();
|
||||
} catch(Exception exc) {
|
||||
throw new RuntimeException("failed to create i2p-destination", exc);
|
||||
}
|
||||
|
||||
// create a session
|
||||
try {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(key);
|
||||
_session = client.createSession(in, tunnel.getClientOptions());
|
||||
} catch(Exception exc) {
|
||||
throw new RuntimeException("failed to create session", exc);
|
||||
}
|
||||
|
||||
// Setup the source. Always expect raw unverified datagrams.
|
||||
_i2pSource = new I2PSource(_session, false, true);
|
||||
|
||||
// Setup the sink. Always send repliable datagrams.
|
||||
if (destination != null && destination.length() > 0) {
|
||||
try {
|
||||
_otherDest = I2PTunnel.destFromName(destination);
|
||||
} catch (DataFormatException dfe) {}
|
||||
if (_otherDest == null) {
|
||||
l.log("Could not resolve " + destination);
|
||||
throw new RuntimeException("failed to create session - could not resolve " + destination);
|
||||
}
|
||||
_i2pSink = new I2PSink(_session, _otherDest, false);
|
||||
} else {
|
||||
_i2pSink = new I2PSinkAnywhere(_session, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually start working on outgoing connections.
|
||||
* Classes should override to start UDP side as well.
|
||||
*
|
||||
* Not specified in I2PTunnelTask but used in both
|
||||
* I2PTunnelClientBase and I2PTunnelServer so let's
|
||||
* implement it here too.
|
||||
*/
|
||||
public void startRunning() {
|
||||
synchronized (startLock) {
|
||||
try {
|
||||
_session.connect();
|
||||
} catch(I2PSessionException exc) {
|
||||
throw new RuntimeException("failed to connect session", exc);
|
||||
}
|
||||
start();
|
||||
startRunning = true;
|
||||
startLock.notify();
|
||||
}
|
||||
open = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2PTunnelTask Methods
|
||||
*
|
||||
* Classes should override to close UDP side as well
|
||||
*/
|
||||
public boolean close(boolean forced) {
|
||||
if (!open) return true;
|
||||
if (_session != null) {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {}
|
||||
}
|
||||
l.log("Closing client " + toString());
|
||||
open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source Methods
|
||||
*
|
||||
* Sets the receiver of the UDP datagrams from I2P
|
||||
* Subclass must call this after constructor
|
||||
* and before start()
|
||||
*/
|
||||
public void setSink(Sink s) {
|
||||
_i2pSource.setSink(s);
|
||||
}
|
||||
|
||||
/** start the source */
|
||||
public void start() {
|
||||
_i2pSource.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sink Methods
|
||||
*
|
||||
* @param to - ignored if configured for a single destination
|
||||
* (we use the dest specified in the constructor)
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
_i2pSink.send(to, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel.udpTunnel;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.I2PTunnelTask;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink {
|
||||
|
||||
private final static Log _log = new Log(I2PTunnelUDPServerBase.class);
|
||||
|
||||
private Object lock = new Object();
|
||||
protected Object slock = new Object();
|
||||
|
||||
private static volatile long __serverId = 0;
|
||||
|
||||
protected Logging l;
|
||||
|
||||
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
private I2PSession _session;
|
||||
private Source _i2pSource;
|
||||
private Sink _i2pSink;
|
||||
|
||||
/**
|
||||
* Base client class that sets up an I2P Datagram server destination.
|
||||
* The UDP side is not implemented here, as there are at least
|
||||
* two possibilities:
|
||||
*
|
||||
* 1) UDP side is a "client"
|
||||
* Example: Streamr Producer
|
||||
* - configure an inbound port
|
||||
* - External application receives no data
|
||||
* - Extending class must have a constructor with a port argument
|
||||
*
|
||||
* 2) UDP side is a client/server
|
||||
* Example: DNS
|
||||
* - configure an inbound port and a destination host and port
|
||||
* - External application sends and receives data
|
||||
* - Extending class must have a constructor with host and 2 port arguments
|
||||
*
|
||||
* So the implementing class must create a UDPSource and/or UDPSink,
|
||||
* and must call setSink().
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*
|
||||
* @author zzz with portions from welterde's streamr
|
||||
*/
|
||||
|
||||
public I2PTunnelUDPServerBase(boolean verify, File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super("UDPServer <- " + privkeyname, notifyThis, tunnel);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(privkey);
|
||||
init(verify, fis, privkeyname, l);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error starting server", ioe);
|
||||
notifyEvent("openServerResult", "error");
|
||||
} finally {
|
||||
if (fis != null)
|
||||
try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void init(boolean verify, InputStream privData, String privkeyname, Logging l) {
|
||||
this.l = l;
|
||||
int portNum = 7654;
|
||||
if (getTunnel().port != null) {
|
||||
try {
|
||||
portNum = Integer.parseInt(getTunnel().port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
// create i2pclient
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
|
||||
try {
|
||||
_session = client.createSession(privData, getTunnel().getClientOptions());
|
||||
} catch(I2PSessionException exc) {
|
||||
throw new RuntimeException("failed to create session", exc);
|
||||
}
|
||||
|
||||
// Setup the source. Always expect repliable datagrams, optionally verify
|
||||
_i2pSource = new I2PSource(_session, verify, false);
|
||||
|
||||
// Setup the sink. Always send raw datagrams.
|
||||
_i2pSink = new I2PSinkAnywhere(_session, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes should override to start UDP side as well.
|
||||
*
|
||||
* Not specified in I2PTunnelTask but used in both
|
||||
* I2PTunnelClientBase and I2PTunnelServer so let's
|
||||
* implement it here too.
|
||||
*/
|
||||
public void startRunning() {
|
||||
//synchronized (startLock) {
|
||||
try {
|
||||
_session.connect();
|
||||
} catch(I2PSessionException exc) {
|
||||
throw new RuntimeException("failed to connect session", exc);
|
||||
}
|
||||
start();
|
||||
//}
|
||||
|
||||
notifyEvent("openServerResult", "ok");
|
||||
open = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read idle timeout for newly-created connections (in
|
||||
* milliseconds). After this time expires without data being reached from
|
||||
* the I2P network, the connection itself will be closed.
|
||||
*/
|
||||
public void setReadTimeout(long ms) {
|
||||
readTimeout = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the read idle timeout for newly-created connections (in
|
||||
* milliseconds).
|
||||
*
|
||||
* @return The read timeout used for connections
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2PTunnelTask Methods
|
||||
*
|
||||
* Classes should override to close UDP side as well
|
||||
*/
|
||||
public boolean close(boolean forced) {
|
||||
if (!open) return true;
|
||||
synchronized (lock) {
|
||||
l.log("Shutting down server " + toString());
|
||||
try {
|
||||
if (_session != null) {
|
||||
_session.destroySession();
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error destroying the session", ex);
|
||||
}
|
||||
l.log("Server shut down.");
|
||||
open = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Source Methods
|
||||
*
|
||||
* Sets the receiver of the UDP datagrams from I2P
|
||||
* Subclass must call this after constructor
|
||||
* and before start()
|
||||
*/
|
||||
public void setSink(Sink s) {
|
||||
_i2pSource.setSink(s);
|
||||
}
|
||||
|
||||
/** start the source */
|
||||
public void start() {
|
||||
_i2pSource.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sink Methods
|
||||
*
|
||||
* @param to
|
||||
*
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
_i2pSink.send(to, data);
|
||||
}
|
||||
}
|
||||
|
@ -351,6 +351,7 @@ public class IndexBean {
|
||||
("httpclient".equals(type)) ||
|
||||
("sockstunnel".equals(type)) ||
|
||||
("connectclient".equals(type)) ||
|
||||
("streamrclient".equals(type)) ||
|
||||
("ircclient".equals(type)));
|
||||
}
|
||||
|
||||
@ -384,8 +385,11 @@ public class IndexBean {
|
||||
else if ("ircclient".equals(internalType)) return "IRC client";
|
||||
else if ("server".equals(internalType)) return "Standard server";
|
||||
else if ("httpserver".equals(internalType)) return "HTTP server";
|
||||
else if ("sockstunnel".equals(internalType)) return "SOCKS 5 proxy";
|
||||
else if ("sockstunnel".equals(internalType)) return "SOCKS 4/4a/5 proxy";
|
||||
else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy";
|
||||
else if ("ircserver".equals(internalType)) return "IRC server";
|
||||
else if ("streamrclient".equals(internalType)) return "Streamr client";
|
||||
else if ("streamrserver".equals(internalType)) return "Streamr server";
|
||||
else return internalType;
|
||||
}
|
||||
|
||||
@ -433,7 +437,8 @@ public class IndexBean {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return "";
|
||||
String rv;
|
||||
if ("client".equals(tun.getType())||"ircclient".equals(tun.getType()))
|
||||
if ("client".equals(tun.getType()) || "ircclient".equals(tun.getType()) ||
|
||||
"streamrclient".equals(tun.getType()))
|
||||
rv = tun.getTargetDestination();
|
||||
else
|
||||
rv = tun.getProxyList();
|
||||
@ -797,7 +802,7 @@ public class IndexBean {
|
||||
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
} else if ("ircclient".equals(_type) || "client".equals(_type)) {
|
||||
} else if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
|
@ -16,10 +16,8 @@ String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
if (EditBean.isClient(type)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else if ("server".equals(type) || "httpserver".equals(type)) {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
} else {
|
||||
%>Invalid tunnel type<%
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
}
|
||||
}
|
||||
%>
|
||||
|
@ -75,7 +75,11 @@
|
||||
</div>
|
||||
|
||||
<div id="accessField" class="rowItem">
|
||||
<% if ("streamrclient".equals(tunnelType)) { %>
|
||||
<label>Target:</label>
|
||||
<% } else { %>
|
||||
<label>Access Point:</label>
|
||||
<% } %>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="port" accesskey="P">
|
||||
@ -87,14 +91,17 @@
|
||||
</label>
|
||||
<input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<% String otherInterface = "";
|
||||
String clientInterface = editBean.getClientInterface(curTunnel);
|
||||
if ("streamrclient".equals(tunnelType)) {
|
||||
otherInterface = clientInterface;
|
||||
} else { %>
|
||||
<div id="reachField" class="rowItem">
|
||||
<label for="reachableBy" accesskey="r">
|
||||
<span class="accessKey">R</span>eachable by:
|
||||
</label>
|
||||
<select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
|
||||
<% String clientInterface = editBean.getClientInterface(curTunnel);
|
||||
String otherInterface = "";
|
||||
if (!("127.0.0.1".equals(clientInterface)) &&
|
||||
<% if (!("127.0.0.1".equals(clientInterface)) &&
|
||||
!("0.0.0.0".equals(clientInterface)) &&
|
||||
(clientInterface != null) &&
|
||||
(clientInterface.trim().length() > 0)) {
|
||||
@ -105,9 +112,18 @@
|
||||
<option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>>LAN Hosts (Please specify your LAN address)</option>
|
||||
</select>
|
||||
</div>
|
||||
<% } // streamrclient %>
|
||||
<div id="otherField" class="rowItem">
|
||||
<label for="reachableByOther" accesskey="O">
|
||||
<% if ("streamrclient".equals(tunnelType)) { %>
|
||||
Host:
|
||||
<% String vvv = otherInterface;
|
||||
if (vvv == null || "".equals(vvv.trim()))
|
||||
out.write(" <font color=\"red\">(required)</font>");
|
||||
%>
|
||||
<% } else { %>
|
||||
<span class="accessKey">O</span>ther:
|
||||
<% } %>
|
||||
</label>
|
||||
<input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />
|
||||
</div>
|
||||
@ -123,7 +139,7 @@
|
||||
</label>
|
||||
<input type="text" size="30" id="proxyList" name="proxyList" title="List of Outproxy I2P destinations" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) {
|
||||
<% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType) || "streamrclient".equals(tunnelType)) {
|
||||
%><div id="destinationField" class="rowItem">
|
||||
<label for="targetDestination" accesskey="T">
|
||||
<span class="accessKey">T</span>unnel Destination:
|
||||
@ -135,8 +151,9 @@
|
||||
<input type="text" size="30" id="targetDestination" name="targetDestination" title="Destination of the Tunnel" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
|
||||
<span class="comment">(name or destination)</span>
|
||||
</div>
|
||||
<% }
|
||||
%><div id="profileField" class="rowItem">
|
||||
<% } %>
|
||||
<% if (!"streamrclient".equals(tunnelType)) { %>
|
||||
<div id="profileField" class="rowItem">
|
||||
<label for="profile" accesskey="f">
|
||||
Pro<span class="accessKey">f</span>ile:
|
||||
</label>
|
||||
@ -160,6 +177,7 @@
|
||||
<input value="true" type="checkbox" id="shared" name="shared" title="Share tunnels with other clients"<%=(editBean.isSharedClient(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
<span class="comment">(Share tunnels with other clients and irc/httpclients? Change requires restart of client proxy)</span>
|
||||
</div>
|
||||
<% } // !streamrclient %>
|
||||
<div id="startupField" class="rowItem">
|
||||
<label for="startOnLoad" accesskey="a">
|
||||
<span class="accessKey">A</span>uto Start:
|
||||
|
@ -82,11 +82,19 @@
|
||||
</div>
|
||||
|
||||
<div id="targetField" class="rowItem">
|
||||
<% if ("streamrserver".equals(tunnelType)) { %>
|
||||
<label>Access Point:</label>
|
||||
<% } else { %>
|
||||
<label>Target:</label>
|
||||
<% } %>
|
||||
</div>
|
||||
<div id="hostField" class="rowItem">
|
||||
<label for="targetHost" accesskey="H">
|
||||
<% if ("streamrserver".equals(tunnelType)) { %>
|
||||
<span class="accessKey">R</span>eachable by:
|
||||
<% } else { %>
|
||||
<span class="accessKey">H</span>ost:
|
||||
<% } %>
|
||||
</label>
|
||||
<input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=editBean.getTargetHost(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
@ -124,6 +132,7 @@
|
||||
</label>
|
||||
<input type="text" size="30" id="privKeyFile" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<% if (!"streamrserver".equals(tunnelType)) { %>
|
||||
<div id="profileField" class="rowItem">
|
||||
<label for="profile" accesskey="f">
|
||||
Pro<span class="accessKey">f</span>ile:
|
||||
@ -134,6 +143,7 @@
|
||||
<option <%=(interactiveProfile == false ? "selected=\"selected\" " : "")%>value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
</select>
|
||||
</div>
|
||||
<% } // !streamrserver %>
|
||||
<div id="destinationField" class="rowItem">
|
||||
<label for="localDestination" accesskey="L">
|
||||
<span class="accessKey">L</span>ocal destination:
|
||||
|
@ -148,8 +148,9 @@
|
||||
<option value="client">Standard</option>
|
||||
<option value="httpclient">HTTP</option>
|
||||
<option value="ircclient">IRC</option>
|
||||
<option value="sockstunnel">SOCKS 5</option>
|
||||
<option value="sockstunnel">SOCKS 4/4a/5</option>
|
||||
<option value="connectclient">CONNECT</option>
|
||||
<option value="streamrclient">Streamr</option>
|
||||
</select>
|
||||
<input class="control" type="submit" value="Create" />
|
||||
</div>
|
||||
@ -260,6 +261,8 @@
|
||||
<select name="type">
|
||||
<option value="server">Standard</option>
|
||||
<option value="httpserver">HTTP</option>
|
||||
<option value="ircserver">IRC</option>
|
||||
<option value="streamrserver">Streamr</option>
|
||||
</select>
|
||||
<input class="control" type="submit" value="Create" />
|
||||
</div>
|
||||
|
@ -2,9 +2,9 @@ package net.i2p.router.web;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.ConvertToHash;
|
||||
|
||||
/**
|
||||
* Support additions via B64 Destkey, B64 Desthash, or blahblah.i2p
|
||||
@ -19,27 +19,12 @@ public class ConfigKeyringHandler extends FormHandler {
|
||||
addFormError("You must enter a destination and a key");
|
||||
return;
|
||||
}
|
||||
Hash h = new Hash();
|
||||
try {
|
||||
h.fromBase64(_peer);
|
||||
} catch (DataFormatException dfe) {}
|
||||
if (h.getData() == null) {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(_peer);
|
||||
h = d.calculateHash();
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
if (h.getData() == null) {
|
||||
Destination d = _context.namingService().lookup(_peer);
|
||||
if (d != null)
|
||||
h = d.calculateHash();
|
||||
}
|
||||
Hash h = ConvertToHash.getHash(_peer);
|
||||
SessionKey sk = new SessionKey();
|
||||
try {
|
||||
sk.fromBase64(_key);
|
||||
} catch (DataFormatException dfe) {}
|
||||
if (h.getData() != null && sk.getData() != null) {
|
||||
if (h != null && h.getData() != null && sk.getData() != null) {
|
||||
_context.keyRing().put(h, sk);
|
||||
addFormNotice("Key for " + h.toBase64() + " added to keyring");
|
||||
} else {
|
||||
|
@ -237,7 +237,7 @@ public class ConfigNetHandler extends FormHandler {
|
||||
|
||||
private void hiddenSwitch() {
|
||||
// Full restart required to generate new keys
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
|
@ -25,18 +25,20 @@ public class ConfigRestartBean {
|
||||
String systemNonce = getNonce();
|
||||
if ( (nonce != null) && (systemNonce.equals(nonce)) && (action != null) ) {
|
||||
if ("shutdownImmediate".equals(action)) {
|
||||
ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD));
|
||||
ctx.router().shutdown(Router.EXIT_HARD); // never returns
|
||||
ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD));
|
||||
//ctx.router().shutdown(Router.EXIT_HARD); // never returns
|
||||
ctx.router().shutdownGracefully(Router.EXIT_HARD); // give the UI time to respond
|
||||
} else if ("cancelShutdown".equals(action)) {
|
||||
ctx.router().cancelGracefulShutdown();
|
||||
} else if ("restartImmediate".equals(action)) {
|
||||
ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
ctx.router().shutdown(Router.EXIT_HARD_RESTART); // never returns
|
||||
ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
//ctx.router().shutdown(Router.EXIT_HARD_RESTART); // never returns
|
||||
ctx.router().shutdownGracefully(Router.EXIT_HARD_RESTART); // give the UI time to respond
|
||||
} else if ("restart".equals(action)) {
|
||||
ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
ctx.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
} else if ("shutdown".equals(action)) {
|
||||
ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
|
||||
ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
|
||||
ctx.router().shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@ -79,9 +81,18 @@ public class ConfigRestartBean {
|
||||
}
|
||||
|
||||
private static boolean isShuttingDown(RouterContext ctx) {
|
||||
return Router.EXIT_GRACEFUL == ctx.router().scheduledGracefulExitCode();
|
||||
return Router.EXIT_GRACEFUL == ctx.router().scheduledGracefulExitCode() ||
|
||||
Router.EXIT_HARD == ctx.router().scheduledGracefulExitCode();
|
||||
}
|
||||
private static boolean isRestarting(RouterContext ctx) {
|
||||
return Router.EXIT_GRACEFUL_RESTART == ctx.router().scheduledGracefulExitCode();
|
||||
return Router.EXIT_GRACEFUL_RESTART == ctx.router().scheduledGracefulExitCode() ||
|
||||
Router.EXIT_HARD_RESTART == ctx.router().scheduledGracefulExitCode();
|
||||
}
|
||||
/** this is for summaryframe.jsp */
|
||||
public static long getRestartTimeRemaining() {
|
||||
RouterContext ctx = ContextHelper.getContext(null);
|
||||
if (ctx.router().gracefulShutdownInProgress())
|
||||
return ctx.router().getShutdownTimeRemaining();
|
||||
return Long.MAX_VALUE/2; // summaryframe.jsp adds a safety factor so we don't want to overflow...
|
||||
}
|
||||
}
|
||||
|
@ -53,31 +53,31 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
if (_action == null) return;
|
||||
|
||||
if ("Shutdown gracefully".equals(_action)) {
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
|
||||
_context.router().shutdownGracefully();
|
||||
addFormNotice("Graceful shutdown initiated");
|
||||
} else if ("Shutdown immediately".equals(_action)) {
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD));
|
||||
_context.router().shutdown(Router.EXIT_HARD);
|
||||
addFormNotice("Shutdown immediately! boom bye bye bad bwoy");
|
||||
} else if ("Cancel graceful shutdown".equals(_action)) {
|
||||
_context.router().cancelGracefulShutdown();
|
||||
addFormNotice("Graceful shutdown cancelled");
|
||||
} else if ("Graceful restart".equals(_action)) {
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
addFormNotice("Graceful restart requested");
|
||||
} else if ("Hard restart".equals(_action)) {
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
_context.router().shutdown(Router.EXIT_HARD_RESTART);
|
||||
addFormNotice("Hard restart requested");
|
||||
} else if ("Rekey and Restart".equals(_action)) {
|
||||
addFormNotice("Rekeying after graceful restart");
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
} else if ("Rekey and Shutdown".equals(_action)) {
|
||||
addFormNotice("Rekeying after graceful shutdown");
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
|
||||
_context.addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL);
|
||||
} else if ("Run I2P on startup".equals(_action)) {
|
||||
installService();
|
||||
|
@ -1,11 +1,15 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.text.DateFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -346,20 +350,16 @@ public class SummaryHelper extends HelperBase {
|
||||
* @return html section summary
|
||||
*/
|
||||
public String getDestinations() {
|
||||
Set clients = _context.clientManager().listClients();
|
||||
// covert the set to a list so we can sort by name and not lose duplicates
|
||||
List clients = new ArrayList(_context.clientManager().listClients());
|
||||
Collections.sort(clients, new AlphaComparator());
|
||||
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<u><b>Local destinations</b></u><br />");
|
||||
|
||||
for (Iterator iter = clients.iterator(); iter.hasNext(); ) {
|
||||
Destination client = (Destination)iter.next();
|
||||
TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(client.calculateHash());
|
||||
TunnelPoolSettings out = _context.tunnelManager().getOutboundSettings(client.calculateHash());
|
||||
String name = (in != null ? in.getDestinationNickname() : null);
|
||||
if (name == null)
|
||||
name = (out != null ? out.getDestinationNickname() : null);
|
||||
if (name == null)
|
||||
name = client.calculateHash().toBase64().substring(0,6);
|
||||
String name = getName(client);
|
||||
|
||||
buf.append("<b>*</b> ").append(name).append("<br />\n");
|
||||
LeaseSet ls = _context.netDb().lookupLeaseSetLocally(client.calculateHash());
|
||||
@ -373,14 +373,38 @@ public class SummaryHelper extends HelperBase {
|
||||
buf.append("<i>No leases</i><br />\n");
|
||||
}
|
||||
buf.append("<a href=\"tunnels.jsp#").append(client.calculateHash().toBase64().substring(0,4));
|
||||
buf.append("\">Details</a> ");
|
||||
buf.append("\" target=\"_top\">Details</a> ");
|
||||
buf.append("<a href=\"configtunnels.jsp#").append(client.calculateHash().toBase64().substring(0,4));
|
||||
buf.append("\">Config</a><br />\n");
|
||||
buf.append("\" target=\"_top\">Config</a><br />\n");
|
||||
}
|
||||
buf.append("<hr />\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private class AlphaComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
String lname = getName((Destination)lhs);
|
||||
String rname = getName((Destination)rhs);
|
||||
if (lname.equals("shared clients"))
|
||||
return -1;
|
||||
if (rname.equals("shared clients"))
|
||||
return 1;
|
||||
return Collator.getInstance().compare(lname, rname);
|
||||
}
|
||||
}
|
||||
|
||||
private String getName(Destination d) {
|
||||
TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(d.calculateHash());
|
||||
String name = (in != null ? in.getDestinationNickname() : null);
|
||||
if (name == null) {
|
||||
TunnelPoolSettings out = _context.tunnelManager().getOutboundSettings(d.calculateHash());
|
||||
name = (out != null ? out.getDestinationNickname() : null);
|
||||
if (name == null)
|
||||
name = d.calculateHash().toBase64().substring(0,6);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many free inbound tunnels we have.
|
||||
*
|
||||
@ -511,4 +535,5 @@ public class SummaryHelper extends HelperBase {
|
||||
public boolean updateAvailable() {
|
||||
return NewsFetcher.getInstance(_context).updateAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ public class UpdateHandler {
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
_context.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
|
@ -89,9 +89,17 @@ class PacketQueue {
|
||||
// so if we retransmit it will use a new tunnel/lease combo
|
||||
expires = rpe.getNextSendTime() - 500;
|
||||
if (expires > 0)
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires);
|
||||
// I2PSessionImpl2
|
||||
//sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires);
|
||||
// I2PSessionMuxedImpl
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires,
|
||||
I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
else
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent);
|
||||
// I2PSessionImpl2
|
||||
//sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, 0);
|
||||
// I2PSessionMuxedImpl
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent,
|
||||
I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
end = _context.clock().now();
|
||||
|
||||
if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
|
Reference in New Issue
Block a user