propagate from branch 'i2p.i2p.zzz.test' (head f19c9c4ae55d6ae82d6c028a06c0fae886da2527)

to branch 'i2p.i2p' (head 78d8ece1514216315644bbef224c62e1e9fbe370)
This commit is contained in:
zzz
2009-02-27 21:25:04 +00:00
60 changed files with 3344 additions and 189 deletions

View File

@ -113,6 +113,10 @@ Applications:
See licenses/LICENSE-I2PTunnel.txt
See licenses/LICENSE-GPLv2.txt
I2PTunnel UDP and Streamr:
By welterde.
See licenses/LICENSE-GPLv2.txt
Jetty 5.1.12:
Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
See licenses/LICENSE-Apache1.1.txt

View File

@ -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) ) {

View File

@ -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
*

View File

@ -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();

View File

@ -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))

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View 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;
}

View File

@ -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;
}

View 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;
}

View 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);
}

View 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();
}

View 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();
}

View 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;
}

View 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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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)) {

View File

@ -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" /><%
}
}
%>

View File

@ -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:

View File

@ -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:

View File

@ -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>

View File

@ -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 {

View File

@ -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);
}

View File

@ -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...
}
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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)) )

View File

@ -23,6 +23,7 @@ import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FortunaRandomSource;
import net.i2p.util.KeyRing;
import net.i2p.util.LogManager;
@ -94,6 +95,7 @@ public class I2PAppContext {
private volatile boolean _randomInitialized;
private volatile boolean _keyGeneratorInitialized;
protected volatile boolean _keyRingInitialized; // used in RouterContext
private Set<Runnable> _shutdownTasks;
/**
@ -152,6 +154,7 @@ public class I2PAppContext {
_elGamalAESEngineInitialized = false;
_logManagerInitialized = false;
_keyRingInitialized = false;
_shutdownTasks = new ConcurrentHashSet(0);
}
/**
@ -557,4 +560,13 @@ public class I2PAppContext {
_randomInitialized = true;
}
}
public void addShutdownTask(Runnable task) {
_shutdownTasks.add(task);
}
public Set<Runnable> getShutdownTasks() {
return new HashSet(_shutdownTasks);
}
}

View File

@ -77,6 +77,6 @@ class I2PClientImpl implements I2PClient {
*
*/
public I2PSession createSession(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
return new I2PSessionImpl2(context, destKeyStream, options); // thread safe
return new I2PSessionMuxedImpl(context, destKeyStream, options); // thread safe and muxed
}
}

View File

@ -40,6 +40,8 @@ public interface I2PSession {
*/
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException;
/** See I2PSessionMuxedImpl for details */
public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException;
/**
* Like sendMessage above, except the key used and the tags sent are exposed to the
@ -71,6 +73,12 @@ public interface I2PSession {
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException;
/** See I2PSessionMuxedImpl for details */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
int proto, int fromport, int toport) throws I2PSessionException;
/** See I2PSessionMuxedImpl for details */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
int proto, int fromport, int toport) throws I2PSessionException;
/** Receive a message that the router has notified the client about, returning
* the payload.
@ -134,4 +142,18 @@ public interface I2PSession {
*
*/
public Destination lookupDest(Hash h) throws I2PSessionException;
/** See I2PSessionMuxedImpl for details */
public void addSessionListener(I2PSessionListener lsnr, int proto, int port);
/** See I2PSessionMuxedImpl for details */
public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port);
/** See I2PSessionMuxedImpl for details */
public void removeListener(int proto, int port);
public static final int PORT_ANY = 0;
public static final int PORT_UNSPECIFIED = 0;
public static final int PROTO_ANY = 0;
public static final int PROTO_UNSPECIFIED = 0;
public static final int PROTO_STREAMING = 6;
public static final int PROTO_DATAGRAM = 17;
}

View File

@ -0,0 +1,135 @@
package net.i2p.client;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/*
* public domain
*/
/**
* Implement multiplexing with a 1-byte 'protocol' and a two-byte 'port'.
* Listeners register with either addListener() or addMuxedListener(),
* depending on whether they want to hear about the
* protocol, from port, and to port for every received message.
*
* This only calls one listener, not all that apply.
*
* @author zzz
*/
public class I2PSessionDemultiplexer implements I2PSessionMuxedListener {
private Log _log;
private Map<Integer, I2PSessionMuxedListener> _listeners;
public I2PSessionDemultiplexer(I2PAppContext ctx) {
_log = ctx.logManager().getLog(I2PSessionDemultiplexer.class);
_listeners = new ConcurrentHashMap();
}
/** unused */
public void messageAvailable(I2PSession session, int msgId, long size) {}
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport ) {
I2PSessionMuxedListener l = findListener(proto, toport);
if (l != null)
l.messageAvailable(session, msgId, size, proto, fromport, toport);
else {
// no listener, throw it out
_log.error("No listener found for proto: " + proto + " port: " + toport + "msg id: " + msgId +
" from pool of " + _listeners.size() + " listeners");
try {
session.receiveMessage(msgId);
} catch (I2PSessionException ise) {}
}
}
public void reportAbuse(I2PSession session, int severity) {
for (I2PSessionMuxedListener l : _listeners.values())
l.reportAbuse(session, severity);
}
public void disconnected(I2PSession session) {
for (I2PSessionMuxedListener l : _listeners.values())
l.disconnected(session);
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
for (I2PSessionMuxedListener l : _listeners.values())
l.errorOccurred(session, message, error);
}
/**
* For those that don't need to hear about the protocol and ports
* in messageAvailable()
* (Streaming lib)
*/
public void addListener(I2PSessionListener l, int proto, int port) {
_listeners.put(key(proto, port), new NoPortsListener(l));
}
/**
* For those that do care
* UDP perhaps
*/
public void addMuxedListener(I2PSessionMuxedListener l, int proto, int port) {
_listeners.put(key(proto, port), l);
}
public void removeListener(int proto, int port) {
_listeners.remove(key(proto, port));
}
/** find the one listener that most specifically matches the request */
private I2PSessionMuxedListener findListener(int proto, int port) {
I2PSessionMuxedListener rv = getListener(proto, port);
if (rv != null) return rv;
if (port != I2PSession.PORT_ANY) { // try any port
rv = getListener(proto, I2PSession.PORT_ANY);
if (rv != null) return rv;
}
if (proto != I2PSession.PROTO_ANY) { // try any protocol
rv = getListener(I2PSession.PROTO_ANY, port);
if (rv != null) return rv;
}
if (proto != I2PSession.PROTO_ANY && port != I2PSession.PORT_ANY) { // try default
rv = getListener(I2PSession.PROTO_ANY, I2PSession.PORT_ANY);
}
return rv;
}
private I2PSessionMuxedListener getListener(int proto, int port) {
return _listeners.get(key(proto, port));
}
private Integer key(int proto, int port) {
return Integer.valueOf(((port << 8) & 0xffff00) | proto);
}
/** for those that don't care about proto and ports */
private static class NoPortsListener implements I2PSessionMuxedListener {
private I2PSessionListener _l;
public NoPortsListener(I2PSessionListener l) {
_l = l;
}
public void messageAvailable(I2PSession session, int msgId, long size) {
throw new IllegalArgumentException("no");
}
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
_l.messageAvailable(session, msgId, size);
}
public void reportAbuse(I2PSession session, int severity) {
_l.reportAbuse(session, severity);
}
public void disconnected(I2PSession session) {
_l.disconnected(session);
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
_l.errorOccurred(session, message, error);
}
}
}

View File

@ -77,12 +77,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected OutputStream _out;
/** who we send events to */
private I2PSessionListener _sessionListener;
protected I2PSessionListener _sessionListener;
/** class that generates new messages */
protected I2CPMessageProducer _producer;
/** map of Long --> MessagePayloadMessage */
private Map<Long, MessagePayloadMessage> _availableMessages;
protected Map<Long, MessagePayloadMessage> _availableMessages;
protected I2PClientMessageHandlerMap _handlerMap;
@ -366,14 +366,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
SimpleScheduler.getInstance().addEvent(new VerifyUsage(mid), 30*1000);
}
private class VerifyUsage implements SimpleTimer.TimedEvent {
protected class VerifyUsage implements SimpleTimer.TimedEvent {
private Long _msgId;
public VerifyUsage(Long id) { _msgId = id; }
public void timeReached() {
MessagePayloadMessage removed = _availableMessages.remove(_msgId);
if (removed != null && !isClosed())
_log.log(Log.CRIT, "Message NOT removed! id=" + _msgId + ": " + removed);
_log.error("Message NOT removed! id=" + _msgId + ": " + removed);
}
}

View File

@ -93,7 +93,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* set to false.
*/
private static final int DONT_COMPRESS_SIZE = 66;
private boolean shouldCompress(int size) {
protected boolean shouldCompress(int size) {
if (size <= DONT_COMPRESS_SIZE)
return false;
String p = getOptions().getProperty("i2cp.gzip");
@ -102,12 +102,35 @@ class I2PSessionImpl2 extends I2PSessionImpl {
return SHOULD_COMPRESS;
}
public void addSessionListener(I2PSessionListener lsnr, int proto, int port) {
throw new IllegalArgumentException("Use MuxedImpl");
}
public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) {
throw new IllegalArgumentException("Use MuxedImpl");
}
public void removeListener(int proto, int port) {
throw new IllegalArgumentException("Use MuxedImpl");
}
public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl");
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
int proto, int fromport, int toport) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl");
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
int proto, int fromport, int toport) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl");
}
@Override
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length);
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException {
return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64), 0);
// we don't do end-to-end crypto any more
//return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64), 0);
return sendMessage(dest, payload, offset, size, null, null, 0);
}
@Override
@ -173,7 +196,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private static final int NUM_TAGS = 50;
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
protected boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException {
SessionKey key = null;
SessionKey newKey = null;

View File

@ -20,7 +20,7 @@ public interface I2PSessionListener {
* size # of bytes.
* @param session session to notify
* @param msgId message number available
* @param size size of the message
* @param size size of the message - why it's a long and not an int is a mystery
*/
void messageAvailable(I2PSession session, int msgId, long size);
@ -42,4 +42,4 @@ public interface I2PSessionListener {
*
*/
void errorOccurred(I2PSession session, String message, Throwable error);
}
}

View File

@ -0,0 +1,320 @@
package net.i2p.client;
/*
* public domain
*/
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
/**
* I2PSession with protocol and ports
*
* Streaming lib has been modified to send I2PSession.PROTO_STREAMING but
* still receives all. It sends with fromPort and toPort = 0, and receives on all ports.
*
* No datagram apps have been modified yet.
* Therefore the compatibility situation is as follows:
*
* Compatibility:
* old streaming -> new streaming: sends proto anything, rcvs proto anything
* new streaming -> old streaming: sends PROTO_STREAMING, ignores rcvd proto
* old datagram -> new datagram: sends proto anything, rcvs proto anything
* new datagram -> old datagram: sends PROTO_DATAGRAM, ignores rcvd proto
* In all the above cases, streaming and datagram receive traffic for the other
* protocol, same as before.
*
* old datagram -> new muxed: doesn't work because the old sends proto 0 but the udp side
* of the mux registers with PROTO_DATAGRAM, so the datagrams
* go to the streaming side, same as before.
* old streaming -> new muxed: works
*
* Typical Usage:
* Streaming + datagrams:
* I2PSocketManager sockMgr = getSocketManager();
* I2PSession session = sockMgr.getSession();
* session.addMuxedSessionListener(myI2PSessionMuxedListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
* * or *
* session.addSessionListener(myI2PSessionListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
* session.sendMessage(dest, payload, I2PSession.PROTO_DATAGRAM, fromPort, toPort);
*
* Datagrams only, with multiple ports:
* I2PClient client = I2PClientFactory.createClient();
* ...
* I2PSession session = client.createSession(...);
* session.addMuxedSessionListener(myI2PSessionMuxedListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
* * or *
* session.addSessionListener(myI2PSessionListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
* session.sendMessage(dest, payload, I2PSession.PROTO_DATAGRAM, fromPort, toPort);
*
* Multiple streaming ports:
* Needs some streaming lib hacking
*
* @author zzz
*/
class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
private I2PSessionDemultiplexer _demultiplexer;
public I2PSessionMuxedImpl(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
super(ctx, destKeyStream, options);
// also stored in _sessionListener but we keep it in _demultipexer
// as well so we don't have to keep casting
_demultiplexer = new I2PSessionDemultiplexer(ctx);
super.setSessionListener(_demultiplexer);
// discards the one in super(), sorry about that... (no it wasn't started yet)
_availabilityNotifier = new MuxedAvailabilityNotifier();
}
/** listen on all protocols and ports */
@Override
public void setSessionListener(I2PSessionListener lsnr) {
_demultiplexer.addListener(lsnr, PROTO_ANY, PORT_ANY);
}
/**
* Listen on specified protocol and port.
*
* An existing listener with the same proto and port is replaced.
* Only the listener with the best match is called back for each message.
*
* @param proto 1-254 or PROTO_ANY for all; recommended:
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param port 1-65535 or PORT_ANY for all
*/
public void addSessionListener(I2PSessionListener lsnr, int proto, int port) {
_demultiplexer.addListener(lsnr, proto, port);
}
/**
* Listen on specified protocol and port, and receive notification
* of proto, fromPort, and toPort for every message.
* @param proto 1-254 or 0 for all; 255 disallowed
* @param port 1-65535 or 0 for all
*/
public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) {
_demultiplexer.addMuxedListener(l, proto, port);
}
/** removes the specified listener (only) */
public void removeListener(int proto, int port) {
_demultiplexer.removeListener(proto, port);
}
@Override
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length, null, null,
0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED);
}
@Override
public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length, null, null, 0, proto, fromport, toport);
}
@Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED);
}
@Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
int proto, int fromport, int toport) throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, proto, fromport, toport);
}
/**
* @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param fromport 1-65535 or 0 for unset
* @param toport 1-65535 or 0 for unset
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
SessionKey keyUsed, Set tagsSent, long expires,
int proto, int fromPort, int toPort)
throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed");
updateActivity();
boolean sc = shouldCompress(size);
if (sc)
payload = DataHelper.compress(payload, offset, size);
else
payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION);
setProto(payload, proto);
setFromPort(payload, fromPort);
setToPort(payload, toPort);
_context.statManager().addRateData("i2cp.tx.msgCompressed", payload.length, 0);
_context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0);
return sendBestEffort(dest, payload, keyUsed, tagsSent, expires);
}
/**
* Receive a payload message and let the app know its available
*/
@Override
public void addNewMessage(MessagePayloadMessage msg) {
Long mid = new Long(msg.getMessageId());
_availableMessages.put(mid, msg);
long id = msg.getMessageId();
byte data[] = msg.getPayload().getUnencryptedData();
if ((data == null) || (data.length <= 0)) {
if (_log.shouldLog(Log.CRIT))
_log.log(Log.CRIT, getPrefix() + "addNewMessage of a message with no unencrypted data",
new Exception("Empty message"));
return;
}
int size = data.length;
if (size < 10) {
_log.error(getPrefix() + "length too short for gzip header: " + size);
return;
}
((MuxedAvailabilityNotifier)_availabilityNotifier).available(id, size, getProto(msg),
getFromPort(msg), getToPort(msg));
SimpleScheduler.getInstance().addEvent(new VerifyUsage(mid), 30*1000);
}
protected class MuxedAvailabilityNotifier extends AvailabilityNotifier {
private LinkedBlockingQueue<MsgData> _msgs;
private boolean _alive;
private static final int POISON_SIZE = -99999;
public MuxedAvailabilityNotifier() {
_msgs = new LinkedBlockingQueue();
}
public void stopNotifying() {
_msgs.clear();
if (_alive) {
_alive = false;
try {
_msgs.put(new MsgData(0, POISON_SIZE, 0, 0, 0));
} catch (InterruptedException ie) {}
}
}
/** unused */
public void available(long msgId, int size) { throw new IllegalArgumentException("no"); }
public void available(long msgId, int size, int proto, int fromPort, int toPort) {
try {
_msgs.put(new MsgData((int)(msgId & 0xffffffff), size, proto, fromPort, toPort));
} catch (InterruptedException ie) {}
}
public void run() {
_alive = true;
while (true) {
MsgData msg;
try {
msg = _msgs.take();
} catch (InterruptedException ie) {
continue;
}
if (msg.size == POISON_SIZE)
break;
try {
_demultiplexer.messageAvailable(I2PSessionMuxedImpl.this, msg.id,
msg.size, msg.proto, msg.fromPort, msg.toPort);
} catch (Exception e) {
_log.error("Error notifying app of message availability");
}
}
}
}
/** let's keep this simple */
private static class MsgData {
public int id, size, proto, fromPort, toPort;
public MsgData(int i, int s, int p, int f, int t) {
id = i;
size = s;
proto = p;
fromPort = f;
toPort = t;
}
}
/**
* No, we couldn't put any protocol byte in front of everything and
* keep backward compatibility. But there are several bytes that
* are unused AND unchecked in the gzip header in releases <= 0.7.
* So let's use 5 of them for a protocol and two 2-byte ports.
*
* Following are all the methods to hide the
* protocol, fromPort, and toPort in the gzip header
*
* The fields used are all ignored on receive in ResettableGzipInputStream
*
* See also ResettableGzipOutputStream.
* Ref: RFC 1952
*
*/
/** OS byte in gzip header */
private static final int PROTO_BYTE = 9;
/** Upper two bytes of MTIME in gzip header */
private static final int FROMPORT_BYTES = 4;
/** Lower two bytes of MTIME in gzip header */
private static final int TOPORT_BYTES = 6;
/** Non-muxed sets the OS byte to 0xff */
private static int getProto(MessagePayloadMessage msg) {
int rv = getByte(msg, PROTO_BYTE) & 0xff;
return rv == 0xff ? PROTO_UNSPECIFIED : rv;
}
/** Non-muxed sets the MTIME bytes to 0 */
private static int getFromPort(MessagePayloadMessage msg) {
return (((getByte(msg, FROMPORT_BYTES) & 0xff) << 8) |
(getByte(msg, FROMPORT_BYTES + 1) & 0xff));
}
/** Non-muxed sets the MTIME bytes to 0 */
private static int getToPort(MessagePayloadMessage msg) {
return (((getByte(msg, TOPORT_BYTES) & 0xff) << 8) |
(getByte(msg, TOPORT_BYTES + 1) & 0xff));
}
private static int getByte(MessagePayloadMessage msg, int i) {
return msg.getPayload().getUnencryptedData()[i] & 0xff;
}
private static void setProto(byte[] payload, int p) {
payload[PROTO_BYTE] = (byte) (p & 0xff);
}
private static void setFromPort(byte[] payload, int p) {
payload[FROMPORT_BYTES] = (byte) ((p >> 8) & 0xff);
payload[FROMPORT_BYTES + 1] = (byte) (p & 0xff);
}
private static void setToPort(byte[] payload, int p) {
payload[TOPORT_BYTES] = (byte) ((p >> 8) & 0xff);
payload[TOPORT_BYTES + 1] = (byte) (p & 0xff);
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.client;
/*
* public domain
*/
/**
* Define a means for the router to asynchronously notify the client that a
* new message is available or the router is under attack.
*
* @author zzz extends I2PSessionListener
*/
public interface I2PSessionMuxedListener extends I2PSessionListener {
/**
* Will be called only if you register via
* setSessionListener() or addSessionListener().
* And if you are doing that, just use I2PSessionListener.
*
* If you register via addSessionListener(),
* this will be called only for the proto(s) and toport(s) you register for.
*
* @param session session to notify
* @param msgId message number available
* @param size size of the message - why it's a long and not an int is a mystery
*/
void messageAvailable(I2PSession session, int msgId, long size);
/**
* Instruct the client that the given session has received a message
*
* Will be called only if you register via addMuxedSessionListener().
* Will be called only for the proto(s) and toport(s) you register for.
*
* @param session session to notify
* @param msgId message number available
* @param size size of the message - why it's a long and not an int is a mystery
* @param proto 1-254 or 0 for unspecified
* @param fromport 1-65535 or 0 for unspecified
* @param toport 1-65535 or 0 for unspecified
*/
void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport);
/** Instruct the client that the session specified seems to be under attack
* and that the client may wish to move its destination to another router.
* @param session session to report abuse to
* @param severity how bad the abuse is
*/
void reportAbuse(I2PSession session, int severity);
/**
* Notify the client that the session has been terminated
*
*/
void disconnected(I2PSession session);
/**
* Notify the client that some error occurred
*
*/
void errorOccurred(I2PSession session, String message, Throwable error);
}

View File

@ -16,8 +16,10 @@ import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
@ -135,4 +137,34 @@ public class HostsTxtNamingService extends NamingService {
}
return null;
}
@Override
public String reverseLookup(Hash h) {
List filenames = getFilenames();
for (int i = 0; i < filenames.size(); i++) {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
Set keyset = hosts.keySet();
Iterator iter = keyset.iterator();
while (iter.hasNext()) {
String host = (String)iter.next();
String key = hosts.getProperty(host);
try {
Destination destkey = new Destination();
destkey.fromBase64(key);
if (h.equals(destkey.calculateHash()))
return host;
} catch (DataFormatException dfe) {}
}
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe);
}
}
return null;
}
}

View File

@ -16,6 +16,7 @@ import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
@ -61,6 +62,7 @@ public abstract class NamingService {
* <code>null</code> if no reverse lookup is possible.
*/
public abstract String reverseLookup(Destination dest);
public String reverseLookup(Hash h) { return null; };
/**
* Check if host name is valid Base64 encoded dest and return this

View File

@ -1,5 +1,6 @@
package net.i2p.stat;
import java.text.Collator;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -178,7 +179,7 @@ public class StatManager {
/** Group name (String) to a Set of stat names, ordered alphabetically */
public Map getStatsByGroup() {
Map groups = new TreeMap();
Map groups = new TreeMap(Collator.getInstance());
for (Iterator iter = _frequencyStats.values().iterator(); iter.hasNext();) {
FrequencyStat stat = (FrequencyStat) iter.next();
if (!groups.containsKey(stat.getGroupName())) groups.put(stat.getGroupName(), new TreeSet());

View File

@ -0,0 +1,76 @@
package net.i2p.util;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
/**
* Convert any kind of destination String to a hash
* Supported:
* Base64 dest
* Base64 dest.i2p
* Base64 Hash
* Base32 Hash
* Base32 desthash.b32.i2p
* example.i2p
*
* @return null on failure
*
* @author zzz
*/
public class ConvertToHash {
public static Hash getHash(String peer) {
if (peer == null)
return null;
Hash h = new Hash();
String peerLC = peer.toLowerCase();
// b64 hash
if (peer.length() == 44 && !peerLC.endsWith(".i2p")) {
try {
h.fromBase64(peer);
} catch (DataFormatException dfe) {}
}
// b64 dest.i2p
if (h.getData() == null && peer.length() >= 520 && peerLC.endsWith(".i2p")) {
try {
Destination d = new Destination();
d.fromBase64(peer.substring(0, peer.length() - 4));
h = d.calculateHash();
} catch (DataFormatException dfe) {}
}
// b64 dest
if (h.getData() == null && peer.length() >= 516 && !peerLC.endsWith(".i2p")) {
try {
Destination d = new Destination();
d.fromBase64(peer);
h = d.calculateHash();
} catch (DataFormatException dfe) {}
}
// b32 hash.b32.i2p
// do this here rather than in naming service so it will work
// even if the leaseset is not found
if (h.getData() == null && peer.length() == 60 && peerLC.endsWith(".b32.i2p")) {
byte[] b = Base32.decode(peer.substring(0, 52));
if (b != null && b.length == Hash.HASH_LENGTH)
h.setData(b);
}
// b32 hash
if (h.getData() == null && peer.length() == 52 && !peerLC.endsWith(".i2p")) {
byte[] b = Base32.decode(peer);
if (b != null && b.length == Hash.HASH_LENGTH)
h.setData(b);
}
// example.i2p
if (h.getData() == null) {
Destination d = I2PAppContext.getGlobalContext().namingService().lookup(peer);
if (d != null)
h = d.calculateHash();
}
if (h.getData() == null)
return null;
return h;
}
}

View File

@ -23,6 +23,9 @@ import freenet.support.CPUInformation.CPUInfo;
import freenet.support.CPUInformation.IntelCPUInfo;
import freenet.support.CPUInformation.UnknownCPUException;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>BigInteger that takes advantage of the jbigi library for the modPow operation,
* which accounts for a massive segment of the processing cost of asymmetric
@ -89,6 +92,9 @@ public class NativeBigInteger extends BigInteger {
* do we want to dump some basic success/failure info to stderr during
* initialization? this would otherwise use the Log component, but this makes
* it easier for other systems to reuse this class
*
* Well, we really want to use Log so if you are one of those "other systems"
* then comment out the I2PAppContext usage below.
*/
private static final boolean _doLog = System.getProperty("jbigi.dontLog") == null;
@ -401,38 +407,32 @@ public class NativeBigInteger extends BigInteger {
boolean loaded = loadGeneric("jbigi");
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Locally optimized native BigInteger loaded from the library path");
info("Locally optimized native BigInteger library loaded from the library path");
} else {
loaded = loadFromResource("jbigi");
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Locally optimized native BigInteger loaded from resource");
info("Locally optimized native BigInteger library loaded from resource");
} else {
loaded = loadFromResource(true);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Optimized native BigInteger library '"+getResourceName(true)+"' loaded from resource");
info("Optimized native BigInteger library '"+getResourceName(true)+"' loaded from resource");
} else {
loaded = loadGeneric(true);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Optimized native BigInteger library '"+getMiddleName(true)+"' loaded from somewhere in the path");
info("Optimized native BigInteger library '"+getMiddleName(true)+"' loaded from somewhere in the path");
} else {
loaded = loadFromResource(false);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Non-optimized native BigInteger library '"+getResourceName(false)+"' loaded from resource");
info("Non-optimized native BigInteger library '"+getResourceName(false)+"' loaded from resource");
} else {
loaded = loadGeneric(false);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Non-optimized native BigInteger library '"+getMiddleName(false)+"' loaded from somewhere in the path");
info("Non-optimized native BigInteger library '"+getMiddleName(false)+"' loaded from somewhere in the path");
} else {
_nativeOk = false;
}
@ -442,16 +442,27 @@ public class NativeBigInteger extends BigInteger {
}
}
}
if (_doLog && !_nativeOk)
System.err.println("INFO: Native BigInteger library jbigi not loaded - using pure java");
if (!_nativeOk) {
warn("Native BigInteger library jbigi not loaded - using pure Java - " +
"poor performance may result - see http://www.i2p2.i2p/jbigi.html for help");
}
}catch(Exception e){
if (_doLog) {
System.err.println("INFO: Native BigInteger library jbigi not loaded, reason: '"+e.getMessage()+"' - using pure java");
e.printStackTrace();
}
warn("Native BigInteger library jbigi not loaded, reason: '"+e.getMessage()+"' - using pure java");
}
}
private static void info(String s) {
if(_doLog)
System.err.println("INFO: " + s);
I2PAppContext.getGlobalContext().logManager().getLog(NativeBigInteger.class).info(s);
}
private static void warn(String s) {
if(_doLog)
System.err.println("WARNING: " + s);
I2PAppContext.getGlobalContext().logManager().getLog(NativeBigInteger.class).warn(s);
}
/**
* <p>Try loading it from an explictly build jbigi.dll / libjbigi.so first, before
* looking into a jbigi.jar for any other libraries.</p>

View File

@ -65,7 +65,6 @@ public class Router {
private I2PThread.OOMEventListener _oomListener;
private ShutdownHook _shutdownHook;
private I2PThread _gracefulShutdownDetector;
private Set _shutdownTasks;
public final static String PROP_CONFIG_FILE = "router.configLocation";
@ -171,7 +170,6 @@ public class Router {
watchdog.setDaemon(true);
watchdog.start();
_shutdownTasks = new HashSet(0);
}
/**
@ -446,13 +444,14 @@ public class Router {
*/
private static final String _rebuildFiles[] = new String[] { "router.info",
"router.keys",
"netDb/my.info",
"connectionTag.keys",
"netDb/my.info", // no longer used
"connectionTag.keys", // never used?
"keyBackup/privateEncryption.key",
"keyBackup/privateSigning.key",
"keyBackup/publicEncryption.key",
"keyBackup/publicSigning.key",
"sessionKeys.dat" };
"sessionKeys.dat" // no longer used
};
static final String IDENTLOG = "identlog.txt";
public static void killKeys() {
@ -490,13 +489,12 @@ public class Router {
*/
public void rebuildNewIdentity() {
killKeys();
try {
for (Iterator iter = _shutdownTasks.iterator(); iter.hasNext(); ) {
Runnable task = (Runnable)iter.next();
for (Runnable task : _context.getShutdownTasks()) {
try {
task.run();
} catch (Throwable t) {
_log.log(Log.CRIT, "Error running shutdown task", t);
}
} catch (Throwable t) {
_log.log(Log.CRIT, "Error running shutdown task", t);
}
// hard and ugly
finalShutdown(EXIT_HARD_RESTART);
@ -781,12 +779,6 @@ public class Router {
buf.setLength(0);
}
public void addShutdownTask(Runnable task) {
synchronized (_shutdownTasks) {
_shutdownTasks.add(task);
}
}
public static final int EXIT_GRACEFUL = 2;
public static final int EXIT_HARD = 3;
public static final int EXIT_OOM = 10;
@ -799,13 +791,12 @@ public class Router {
I2PThread.removeOOMEventListener(_oomListener);
// Run the shutdown hooks first in case they want to send some goodbye messages
// Maybe we need a delay after this too?
try {
for (Iterator iter = _shutdownTasks.iterator(); iter.hasNext(); ) {
Runnable task = (Runnable)iter.next();
for (Runnable task : _context.getShutdownTasks()) {
try {
task.run();
} catch (Throwable t) {
_log.log(Log.CRIT, "Error running shutdown task", t);
}
} catch (Throwable t) {
_log.log(Log.CRIT, "Error running shutdown task", t);
}
try { _context.clientManager().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the client manager", t); }
try { _context.jobQueue().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the job queue", t); }
@ -859,6 +850,10 @@ public class Router {
public void shutdownGracefully() {
shutdownGracefully(EXIT_GRACEFUL);
}
/**
* Call this with EXIT_HARD or EXIT_HARD_RESTART for a non-blocking,
* hard, non-graceful shutdown with a brief delay to allow a UI response
*/
public void shutdownGracefully(int exitCode) {
_gracefulExitCode = exitCode;
_config.setProperty(PROP_SHUTDOWN_IN_PROGRESS, "true");
@ -887,7 +882,9 @@ public class Router {
}
/** How long until the graceful shutdown will kill us? */
public long getShutdownTimeRemaining() {
if (_gracefulExitCode <= 0) return -1;
if (_gracefulExitCode <= 0) return -1; // maybe Long.MAX_VALUE would be better?
if (_gracefulExitCode == EXIT_HARD || _gracefulExitCode == EXIT_HARD_RESTART)
return 0;
long exp = _context.tunnelManager().getLastParticipatingExpiration();
if (exp < 0)
return -1;
@ -906,9 +903,20 @@ public class Router {
while (true) {
boolean shutdown = (null != _config.getProperty(PROP_SHUTDOWN_IN_PROGRESS));
if (shutdown) {
if (_context.tunnelManager().getParticipatingCount() <= 0) {
if (_log.shouldLog(Log.CRIT))
if (_gracefulExitCode == EXIT_HARD || _gracefulExitCode == EXIT_HARD_RESTART ||
_context.tunnelManager().getParticipatingCount() <= 0) {
if (_gracefulExitCode == EXIT_HARD)
_log.log(Log.CRIT, "Shutting down after a brief delay");
else if (_gracefulExitCode == EXIT_HARD_RESTART)
_log.log(Log.CRIT, "Restarting after a brief delay");
else
_log.log(Log.CRIT, "Graceful shutdown progress - no more tunnels, safe to die");
// Allow time for a UI reponse
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(2*1000);
}
} catch (InterruptedException ie) {}
shutdown(_gracefulExitCode);
return;
} else {

View File

@ -34,6 +34,8 @@ import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Send a client message out a random outbound tunnel and into a random inbound
@ -98,6 +100,10 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
*/
private static final int BUNDLE_PROBABILITY_DEFAULT = 100;
private static final Object _initializeLock = new Object();
private static boolean _initialized = false;
private static final int CLEAN_INTERVAL = 5*60*1000;
/**
* Send the sucker
*/
@ -105,20 +111,26 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
super(ctx);
_log = ctx.logManager().getLog(OutboundClientMessageOneShotJob.class);
ctx.statManager().createFrequencyStat("client.sendMessageFailFrequency", "How often does a client fail to send a message?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.sendMessageSize", "How large are messages sent by the client?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.sendAckTime", "Message round trip time", "ClientMessages", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionTunnel", "How lagged our tunnels are when a send times out?", "ClientMessages", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionMessage", "How fast we process messages locally when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionInbound", "How much faster we are receiving data than our average bps when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.leaseSetFoundLocally", "How often we tried to look for a leaseSet and found it locally?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.leaseSetFoundRemoteTime", "How long we tried to look for a remote leaseSet (when we succeeded)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.leaseSetFailedRemoteTime", "How long we tried to look for a remote leaseSet (when we failed)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchPrepareTime", "How long until we've queued up the dispatch job (since we started)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchTime", "How long until we've dispatched the message (since we started)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchSendTime", "How long the actual dispatching takes?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchNoTunnels", "How long after start do we run out of tunnels to send/receive with?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchNoACK", "Repeated message sends to a peer (no ack required)", "ClientMessages", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l });
synchronized (_initializeLock) {
if (!_initialized) {
SimpleScheduler.getInstance().addPeriodicEvent(new OCMOSJCacheCleaner(ctx), CLEAN_INTERVAL, CLEAN_INTERVAL);
ctx.statManager().createFrequencyStat("client.sendMessageFailFrequency", "How often does a client fail to send a message?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.sendMessageSize", "How large are messages sent by the client?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.sendAckTime", "Message round trip time", "ClientMessages", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionTunnel", "How lagged our tunnels are when a send times out?", "ClientMessages", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionMessage", "How fast we process messages locally when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionInbound", "How much faster we are receiving data than our average bps when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.leaseSetFoundLocally", "How often we tried to look for a leaseSet and found it locally?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.leaseSetFoundRemoteTime", "How long we tried to look for a remote leaseSet (when we succeeded)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.leaseSetFailedRemoteTime", "How long we tried to look for a remote leaseSet (when we failed)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchPrepareTime", "How long until we've queued up the dispatch job (since we started)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchTime", "How long until we've dispatched the message (since we started)?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchSendTime", "How long the actual dispatching takes?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchNoTunnels", "How long after start do we run out of tunnels to send/receive with?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.dispatchNoACK", "Repeated message sends to a peer (no ack required)", "ClientMessages", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l });
_initialized = true;
}
}
long timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
_clientMessage = msg;
_clientMessageId = msg.getMessageId();
@ -201,7 +213,6 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* Key the cache on the source+dest pair.
*/
private static HashMap _leaseSetCache = new HashMap();
private static long _lscleanTime = 0;
private LeaseSet getReplyLeaseSet(boolean force) {
LeaseSet newLS = getContext().netDb().lookupLeaseSetLocally(_from.calculateHash());
if (newLS == null)
@ -235,10 +246,6 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
// If the last leaseSet we sent him is still good, don't bother sending again
long now = getContext().clock().now();
synchronized (_leaseSetCache) {
if (now - _lscleanTime > 5*60*1000) { // clean out periodically
cleanLeaseSetCache(_leaseSetCache);
_lscleanTime = now;
}
if (!force) {
LeaseSet ls = (LeaseSet) _leaseSetCache.get(hashPair());
if (ls != null) {
@ -306,7 +313,6 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
*
*/
private static HashMap _leaseCache = new HashMap();
private static long _lcleanTime = 0;
private boolean getNextLease() {
_leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash());
if (_leaseSet == null) {
@ -319,10 +325,6 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
// Use the same lease if it's still good
// Even if _leaseSet changed, _leaseSet.getEncryptionKey() didn't...
synchronized (_leaseCache) {
if (now - _lcleanTime > 5*60*1000) { // clean out periodically
cleanLeaseCache(_leaseCache);
_lcleanTime = now;
}
_lease = (Lease) _leaseCache.get(hashPair());
if (_lease != null) {
// if outbound tunnel length == 0 && lease.firsthop.isBacklogged() don't use it ??
@ -607,7 +609,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* (needed for cleanTunnelCache)
* 44 = 32 * 4 / 3
*/
private Hash sourceFromHashPair(String s) {
private static Hash sourceFromHashPair(String s) {
return new Hash(Base64.decode(s.substring(44, 88)));
}
@ -648,8 +650,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* Clean out old leaseSets from a set.
* Caller must synchronize on tc.
*/
private void cleanLeaseSetCache(HashMap tc) {
long now = getContext().clock().now();
private static void cleanLeaseSetCache(RouterContext ctx, HashMap tc) {
long now = ctx.clock().now();
List deleteList = new ArrayList();
for (Iterator iter = tc.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter.next();
@ -668,7 +670,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* Clean out old leases from a set.
* Caller must synchronize on tc.
*/
private void cleanLeaseCache(HashMap tc) {
private static void cleanLeaseCache(HashMap tc) {
List deleteList = new ArrayList();
for (Iterator iter = tc.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter.next();
@ -687,13 +689,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* Clean out old tunnels from a set.
* Caller must synchronize on tc.
*/
private void cleanTunnelCache(HashMap tc) {
private static void cleanTunnelCache(RouterContext ctx, HashMap tc) {
List deleteList = new ArrayList();
for (Iterator iter = tc.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter.next();
String k = (String) entry.getKey();
TunnelInfo tunnel = (TunnelInfo) entry.getValue();
if (!getContext().tunnelManager().isValidTunnel(sourceFromHashPair(k), tunnel))
if (!ctx.tunnelManager().isValidTunnel(sourceFromHashPair(k), tunnel))
deleteList.add(k);
}
for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) {
@ -702,6 +704,25 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
}
}
private static class OCMOSJCacheCleaner implements SimpleTimer.TimedEvent {
private RouterContext _ctx;
private OCMOSJCacheCleaner(RouterContext ctx) {
_ctx = ctx;
}
public void timeReached() {
synchronized(_leaseSetCache) {
cleanLeaseSetCache(_ctx, _leaseSetCache);
}
synchronized(_leaseCache) {
cleanLeaseCache(_leaseCache);
}
synchronized(_tunnelCache) {
cleanTunnelCache(_ctx, _tunnelCache);
cleanTunnelCache(_ctx, _backloggedTunnelCache);
}
}
}
/**
* Use the same outbound tunnel as we did for the same destination previously,
* if possible, to keep the streaming lib happy
@ -712,16 +733,10 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
*/
private static HashMap _tunnelCache = new HashMap();
private static HashMap _backloggedTunnelCache = new HashMap();
private static long _cleanTime = 0;
private TunnelInfo selectOutboundTunnel(Destination to) {
TunnelInfo tunnel;
long now = getContext().clock().now();
synchronized (_tunnelCache) {
if (now - _cleanTime > 5*60*1000) { // clean out periodically
cleanTunnelCache(_tunnelCache);
cleanTunnelCache(_backloggedTunnelCache);
_cleanTime = now;
}
/**
* If old tunnel is valid and no longer backlogged, use it.
* This prevents an active anonymity attack, where a peer could tell