From f3143d8b3d65e7add8dcf9ac5182652182fde878 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 18 Feb 2009 20:54:55 +0000 Subject: [PATCH 001/112] case insensitive sort on stat groups --- core/java/src/net/i2p/stat/StatManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/stat/StatManager.java b/core/java/src/net/i2p/stat/StatManager.java index 4c5c69c791..56af55f71d 100644 --- a/core/java/src/net/i2p/stat/StatManager.java +++ b/core/java/src/net/i2p/stat/StatManager.java @@ -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()); From fbe7e42f46b08fc1d4d8cf15676d0779c986e5d3 Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 20 Feb 2009 17:53:17 +0000 Subject: [PATCH 002/112] fixed a NPE --- core/java/src/net/i2p/data/PrivateKeyFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index d9e52aecc0..b5d68ee411 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -261,7 +261,7 @@ public class PrivateKeyFile { public String toString() { StringBuffer s = new StringBuffer(128); s.append("Dest: "); - s.append(this.dest.toBase64()); + s.append(this.dest != null ? this.dest.toBase64() : "null"); s.append("\nContains: "); s.append(this.dest); s.append("\nPrivate Key: "); From f4c3607c4d7725997fc5d9627d70a305d3a2d0e4 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 22 Feb 2009 00:35:24 +0000 Subject: [PATCH 003/112] * I2PTunnel: - Add new IRCServer tunnel type - Catch OOMs in HTTPServer - Name the IRCClient filter threads --- .../java/src/net/i2p/i2ptunnel/I2PTunnel.java | 49 +++++ .../i2p/i2ptunnel/I2PTunnelHTTPServer.java | 9 + .../net/i2p/i2ptunnel/I2PTunnelIRCClient.java | 4 +- .../net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 193 ++++++++++++++++++ .../net/i2p/i2ptunnel/TunnelController.java | 15 +- .../src/net/i2p/i2ptunnel/web/IndexBean.java | 1 + apps/i2ptunnel/jsp/edit.jsp | 4 +- apps/i2ptunnel/jsp/index.jsp | 1 + 8 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index b72ae18b31..18b517cd2c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -234,6 +234,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)) { @@ -383,6 +385,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 "); + 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 diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 658dd5e327..536844af9c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -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(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java index e6708aa21d..5b223b1a42 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java @@ -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)) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java new file mode 100644 index 0000000000..970d90ff01 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -0,0 +1,193 @@ +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, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { + super(host, port, privData, l, notifyThis, tunnel); + initCloak(tunnel); + } + + 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); + } + + public I2PTunnelIRCServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { + super(host, port, privData, 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(); + } +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 3c9640ce5a..82b253985f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -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(); } @@ -148,6 +148,8 @@ public class TunnelController implements Logging { startServer(); } else if ("httpserver".equals(type)) { startHttpServer(); + } else if ("ircserver".equals(type)) { + startIrcServer(); } else { if (_log.shouldLog(Log.ERROR)) _log.error("Cannot start tunnel - unknown type [" + type + "]"); @@ -274,6 +276,17 @@ public class TunnelController implements Logging { _running = true; } + private void startIrcServer() { + setI2CPOptions(); + setSessionOptions(); + String targetHost = getTargetHost(); + String targetPort = getTargetPort(); + String privKeyFile = getPrivKeyFile(); + _tunnel.runIrcServer(new String[] { targetHost, targetPort, privKeyFile }, this); + acquire(); + _running = true; + } + private void setListenOn() { String listenOn = getListenOnInterface(); if ( (listenOn != null) && (listenOn.length() > 0) ) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 46b5557729..87d8e26f69 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -386,6 +386,7 @@ public class IndexBean { else if ("httpserver".equals(internalType)) return "HTTP server"; else if ("sockstunnel".equals(internalType)) return "SOCKS 5 proxy"; else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy"; + else if ("ircserver".equals(internalType)) return "IRC server"; else return internalType; } diff --git a/apps/i2ptunnel/jsp/edit.jsp b/apps/i2ptunnel/jsp/edit.jsp index 67fdf016c3..b58798b202 100644 --- a/apps/i2ptunnel/jsp/edit.jsp +++ b/apps/i2ptunnel/jsp/edit.jsp @@ -16,10 +16,8 @@ String tun = request.getParameter("tunnel"); int curTunnel = -1; if (EditBean.isClient(type)) { %><% - } else if ("server".equals(type) || "httpserver".equals(type)) { - %><% } else { - %>Invalid tunnel type<% + %><% } } %> diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index b96236ae14..77303267c9 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -260,6 +260,7 @@ From 3603cc23ee0725256a0321e6fba59c190c1e37fe Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 22 Feb 2009 02:58:00 +0000 Subject: [PATCH 004/112] add socks 4/4a support --- .../i2p/i2ptunnel/socks/SOCKS4aServer.java | 283 ++++++++++++++++++ .../i2ptunnel/socks/SOCKSServerFactory.java | 4 + .../src/net/i2p/i2ptunnel/web/IndexBean.java | 2 +- apps/i2ptunnel/jsp/index.jsp | 2 +- 4 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java new file mode 100644 index 0000000000..2745cb0fa9 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java @@ -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 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; + } +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java index 67a52d6889..80dfacb6a0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java @@ -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); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 87d8e26f69..045ea5e583 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -384,7 +384,7 @@ 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 return internalType; diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index 77303267c9..7787eb1f52 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -148,7 +148,7 @@ - + From 8bce2fd7a2cf10d25114dcfc3752a7e4b8b8b29f Mon Sep 17 00:00:00 2001 From: sponge Date: Sun, 22 Feb 2009 07:04:31 +0000 Subject: [PATCH 005/112] Hopeful BOB fixes for orphaned tunnels. Additional comments in TCPio addressing performance. --- apps/BOB/src/net/i2p/BOB/I2Plistener.java | 2 +- apps/BOB/src/net/i2p/BOB/MUXlisten.java | 37 ++++++++++++++++++----- apps/BOB/src/net/i2p/BOB/TCPio.java | 19 ++++++++++++ apps/BOB/src/net/i2p/BOB/TCPlistener.java | 2 +- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java index 1561b7a222..c59683270e 100644 --- a/apps/BOB/src/net/i2p/BOB/I2Plistener.java +++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java @@ -70,7 +70,7 @@ public class I2Plistener implements Runnable { boolean g = false; I2PSocket sessSocket = null; - serverSocket.setSoTimeout(100); + serverSocket.setSoTimeout(50); database.getReadLock(); info.getReadLock(); if(info.exists("INPORT")) { diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java index bd52e27fd2..89ab53fe62 100644 --- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java +++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java @@ -173,7 +173,7 @@ die: { boolean spin = true; while(spin) { try { - Thread.sleep(1000); //sleep for 1000 ms (One second) + Thread.sleep(200); //sleep for 200 ms (Two thenths second) } catch(InterruptedException e) { // nop } @@ -213,14 +213,21 @@ die: { } } // die + try { + Thread.sleep(500); //sleep for 500 ms (One half second) + } catch(InterruptedException ex) { + // nop + } // wait for child threads and thread groups to die // System.out.println("MUXlisten: waiting for children"); - while(tg.activeCount() + tg.activeGroupCount() != 0) { + if(tg.activeCount() + tg.activeGroupCount() != 0) { tg.interrupt(); // unwedge any blocking threads. - try { - Thread.sleep(100); //sleep for 100 ms (One tenth second) - } catch(InterruptedException ex) { - // nop + while(tg.activeCount() + tg.activeGroupCount() != 0) { + try { + Thread.sleep(100); //sleep for 100 ms (One tenth second) + } catch(InterruptedException ex) { + // nop + } } } tg.destroy(); @@ -260,17 +267,33 @@ die: { } // This is here to catch when something fucks up REALLY bad. if(tg != null) { - while(tg.activeCount() + tg.activeGroupCount() != 0) { + if(tg.activeCount() + tg.activeGroupCount() != 0) { tg.interrupt(); // unwedge any blocking threads. + while(tg.activeCount() + tg.activeGroupCount() != 0) { try { Thread.sleep(100); //sleep for 100 ms (One tenth second) } catch(InterruptedException ex) { // nop } + } } tg.destroy(); // Zap reference to the ThreadGroup so the JVM can GC it. tg = null; } + + // Lastly try to close things again. + if(this.come_in) { + try { + listener.close(); + } catch(IOException e) { + } + } + try { + socketManager.destroySocketManager(); + } catch(Exception e) { + // nop + } + } } diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java index 25290bcdcf..41bb7cbe49 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPio.java +++ b/apps/BOB/src/net/i2p/BOB/TCPio.java @@ -56,9 +56,28 @@ public class TCPio implements Runnable { * Copy from source to destination... * and yes, we are totally OK to block here on writes, * The OS has buffers, and I intend to use them. + * We send an interrupt signal to the threadgroup to + * unwedge any pending writes. * */ public void run() { + /* + * NOTE: + * The write method of OutputStream calls the write method of + * one argument on each of the bytes to be written out. + * Subclasses are encouraged to override this method and provide + * a more efficient implementation. + * + * So, is this really a performance problem? + * Should we expand to several bytes? + * I don't believe there would be any gain, since read method + * has the same reccomendations. If anyone has a better way to + * do this, I'm interested in performance improvements. + * + * --Sponge + * + */ + int b; byte a[] = new byte[1]; boolean spin = true; diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java index 99ae047d31..30380a55dd 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPlistener.java +++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java @@ -77,7 +77,7 @@ public class TCPlistener implements Runnable { } try { Socket server = new Socket(); - listener.setSoTimeout(1000); + listener.setSoTimeout(50); // Half of the expected time from MUXlisten info.releaseReadLock(); database.releaseReadLock(); while(spin) { From 532077a4c15b6bf4b2f634a7d695de0eb96e55d1 Mon Sep 17 00:00:00 2001 From: sponge Date: Sun, 22 Feb 2009 07:26:08 +0000 Subject: [PATCH 006/112] BOB version bump. Router Build bump. --- apps/BOB/src/net/i2p/BOB/DoCMDS.java | 2 +- history.txt | 4 ++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/BOB/src/net/i2p/BOB/DoCMDS.java b/apps/BOB/src/net/i2p/BOB/DoCMDS.java index 6033e303bf..099d69feca 100644 --- a/apps/BOB/src/net/i2p/BOB/DoCMDS.java +++ b/apps/BOB/src/net/i2p/BOB/DoCMDS.java @@ -46,7 +46,7 @@ public class DoCMDS implements Runnable { // FIX ME // I need a better way to do versioning, but this will do for now. - public static final String BMAJ = "00", BMIN = "00", BREV = "03", BEXT = ""; + public static final String BMAJ = "00", BMIN = "00", BREV = "04", BEXT = ""; public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT; private Socket server; private Properties props; diff --git a/history.txt b/history.txt index 62c4430e91..ec81d5a43a 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,7 @@ +2009-02-22 sponge + * BOB: Orphan tunnel issue fix, bump BOB version + * bump to Build 6 + 2009-02-16 zzz * Streaming lib: Plug timer leak, don't send keepalives after close, don't disconnect hard after close diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 5203f2360b..c089489691 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 5; + public final static long BUILD = 6; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 720aa704c4c5b8750e123045131f5ce8a00cdb97 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 23 Feb 2009 05:09:44 +0000 Subject: [PATCH 007/112] port streamr to i2ptunnel --- LICENSE.txt | 4 + .../java/src/net/i2p/i2ptunnel/I2PTunnel.java | 82 +++++++ .../net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 9 - .../net/i2p/i2ptunnel/TunnelController.java | 59 +++-- .../i2p/i2ptunnel/streamr/MultiSource.java | 60 +++++ .../src/net/i2p/i2ptunnel/streamr/Pinger.java | 59 +++++ .../i2ptunnel/streamr/StreamrConsumer.java | 64 +++++ .../i2ptunnel/streamr/StreamrProducer.java | 70 ++++++ .../net/i2p/i2ptunnel/streamr/Subscriber.java | 75 ++++++ .../src/net/i2p/i2ptunnel/udp/I2PSink.java | 70 ++++++ .../i2p/i2ptunnel/udp/I2PSinkAnywhere.java | 67 ++++++ .../src/net/i2p/i2ptunnel/udp/I2PSource.java | 123 ++++++++++ .../java/src/net/i2p/i2ptunnel/udp/Sink.java | 17 ++ .../src/net/i2p/i2ptunnel/udp/Source.java | 15 ++ .../src/net/i2p/i2ptunnel/udp/Stream.java | 15 ++ .../src/net/i2p/i2ptunnel/udp/UDPSink.java | 74 ++++++ .../src/net/i2p/i2ptunnel/udp/UDPSource.java | 83 +++++++ .../udpTunnel/I2PTunnelUDPClientBase.java | 218 ++++++++++++++++++ .../udpTunnel/I2PTunnelUDPServerBase.java | 212 +++++++++++++++++ .../src/net/i2p/i2ptunnel/web/IndexBean.java | 8 +- apps/i2ptunnel/jsp/editClient.jsp | 30 ++- apps/i2ptunnel/jsp/editServer.jsp | 8 + apps/i2ptunnel/jsp/index.jsp | 2 + 23 files changed, 1375 insertions(+), 49 deletions(-) create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java diff --git a/LICENSE.txt b/LICENSE.txt index 324f532c61..e937985488 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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 diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index 18b517cd2c..dc9dfd2fcc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -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; @@ -248,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)) { @@ -800,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 "); + 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 "); + l.log(" creates a tunnel that sends streaming data."); + notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1)); + } + } + /** * Specify the i2cp host and port * diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 970d90ff01..aa95e526c8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -61,21 +61,12 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { * @throws IllegalArgumentException if the I2PTunnel does not contain * valid config to contact the router */ - public I2PTunnelIRCServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { - super(host, port, privData, l, notifyThis, tunnel); - initCloak(tunnel); - } 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); } - public I2PTunnelIRCServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { - super(host, port, privData, 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(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 82b253985f..6c5fa4eb99 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -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,21 +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(); @@ -167,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(); @@ -182,31 +185,39 @@ 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 */ + private void startStreamrServer() { + String listenPort = getTargetPort(); + String privKeyFile = getPrivKeyFile(); + _tunnel.runStreamrServer(new String[] { listenPort, privKeyFile }, this); } /** @@ -242,49 +253,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() { - setI2CPOptions(); - setSessionOptions(); String targetHost = getTargetHost(); String targetPort = getTargetPort(); String privKeyFile = getPrivKeyFile(); _tunnel.runIrcServer(new String[] { targetHost, targetPort, privKeyFile }, this); - acquire(); - _running = true; } private void setListenOn() { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java new file mode 100644 index 0000000000..13d9b55202 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java @@ -0,0 +1,60 @@ +/* + * 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(); + } + + public void setSink(Sink sink) { + this.sink = sink; + } + + public void start() {} + + 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 sinks; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java new file mode 100644 index 0000000000..a3a7975361 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java @@ -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; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java new file mode 100644 index 0000000000..3fc1d881b9 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java @@ -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.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(); + } + + public boolean close(boolean forced) { + // send unsubscribe-message + this.pinger.stop(); + return super.close(forced); + } + + + + + + + + + + + private Sink sink; + private Pinger pinger; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java new file mode 100644 index 0000000000..d722c5f95c --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java @@ -0,0 +1,70 @@ +/* + * 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(); + } + + public boolean close(boolean forced) { + // need some stop() methods in UDPSource and MultiSource + return super.close(forced); + } + + + + + + + + + + + private MultiSource multi; + private Source server; + private Sink subscriber; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java new file mode 100644 index 0000000000..97abdb8890 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java @@ -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(); + } + + 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 subscriptions; + private MultiSource multi; + private Source server; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java new file mode 100644 index 0000000000..3cbccf139e --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java @@ -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; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java new file mode 100644 index 0000000000..09385d46fe --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java @@ -0,0 +1,67 @@ +/* + * 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 + 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; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java new file mode 100644 index 0000000000..0b54747772 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java @@ -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 queue; + protected Sink sink; + protected Thread thread; + protected boolean verify; + protected boolean raw; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java new file mode 100644 index 0000000000..49e3e47a3e --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java @@ -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); +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java new file mode 100644 index 0000000000..f65d03b196 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java @@ -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(); +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java new file mode 100644 index 0000000000..b8b57e696c --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java @@ -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(); +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java new file mode 100644 index 0000000000..15feba6156 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java @@ -0,0 +1,74 @@ +/* + * 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) { + // 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(); + } + } + + + + + + + + + + + + + + + + + + + + + + + protected DatagramSocket sock; + protected InetAddress remoteHost; + protected int remotePort; + +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java new file mode 100644 index 0000000000..c54a984b0a --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java @@ -0,0 +1,83 @@ +/* + * 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); + } + + 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(); + } + } + } + + + + + + + + + + + + + + + + + + protected DatagramSocket sock; + protected Sink sink; + protected Thread thread; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java new file mode 100644 index 0000000000..0123be6eab --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java @@ -0,0 +1,218 @@ +/* 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); + } + + //configurePool(tunnel); + + } + + /** + * 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(); + } + + if (open && listenerReady) { + notifyEvent("openBaseClientResult", "ok"); + } else { + l.log("Error listening - please see the logs!"); + notifyEvent("openBaseClientResult", "error"); + } + } + + /** + * 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()); + 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); + } +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java new file mode 100644 index 0000000000..fe129fb131 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java @@ -0,0 +1,212 @@ +/* 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; + + private 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(); + //} + + l.log("Ready!"); + 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); + } +} + diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 045ea5e583..6fcd9f2fe3 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -351,6 +351,7 @@ public class IndexBean { ("httpclient".equals(type)) || ("sockstunnel".equals(type)) || ("connectclient".equals(type)) || + ("streamrclient".equals(type)) || ("ircclient".equals(type))); } @@ -387,6 +388,8 @@ public class IndexBean { 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; } @@ -434,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(); @@ -798,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)) { diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 3e4c3ecd80..6a796eb7df 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -75,7 +75,11 @@
+ <% if ("streamrclient".equals(tunnelType)) { %> + + <% } else { %> + <% } %>
+ <% String otherInterface = ""; + String clientInterface = editBean.getClientInterface(curTunnel); + if ("streamrclient".equals(tunnelType)) { + otherInterface = clientInterface; + } else { %>
+ <% } // streamrclient %>
@@ -123,7 +139,7 @@ - <% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { + <% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType) || "streamrclient".equals(tunnelType)) { %>
- <% } - %>
+ <% } %> + <% if (!"streamrclient".equals(tunnelType)) { %> +
@@ -160,6 +177,7 @@ class="tickbox" /> (Share tunnels with other clients and irc/httpclients? Change requires restart of client proxy)
+ <% } // !streamrclient %>
+ <% if ("streamrserver".equals(tunnelType)) { %> + + <% } else { %> + <% } %>
+ <% if (!"streamrserver".equals(tunnelType)) { %>
+ <% } // !streamrserver %>
+ <% if (!"streamrserver".equals(tunnelType)) { %>
+ <% } // !streamrserver %>
@@ -261,6 +262,7 @@ +
From 7e21afe6a6c2ce3d84a8973393d5ac60154d7f90 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Feb 2009 22:59:59 +0000 Subject: [PATCH 008/112] sort the summary bar destinations --- .../src/net/i2p/router/web/SummaryHelper.java | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index 2e56e858bc..47a07c3746 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -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("Local destinations
"); 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("* ").append(name).append("
\n"); LeaseSet ls = _context.netDb().lookupLeaseSetLocally(client.calculateHash()); @@ -373,14 +373,38 @@ public class SummaryHelper extends HelperBase { buf.append("No leases
\n"); } buf.append("Details "); + buf.append("\" target=\"_top\">Details "); buf.append("Config
\n"); + buf.append("\" target=\"_top\">Config
\n"); } buf.append("
\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(); } + } From 7a684c160b50988cb9103d6a0033af64eb18bd08 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Feb 2009 23:15:26 +0000 Subject: [PATCH 009/112] * Routerconsole: - Thread hard shutdown and restart requests from the routerconsole, and add a delay even if no tunnels, to allow time for a UI response --- .../net/i2p/router/web/ConfigRestartBean.java | 19 +++++++++--- router/java/src/net/i2p/router/Router.java | 30 +++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java index 3c9fe1bbf4..75a8108c50 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java @@ -26,12 +26,14 @@ public class ConfigRestartBean { 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.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.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.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); @@ -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... } } diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 033678924c..77e4b19681 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -446,13 +446,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() { @@ -859,6 +860,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 +892,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 +913,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 { From 559653f0ab532352503b527f38b326745f176940 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Feb 2009 23:18:12 +0000 Subject: [PATCH 010/112] clean up OCMOSJ cache cleaner --- .../OutboundClientMessageOneShotJob.java | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index 0515d5c344..0e858ef779 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -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 From 6484005569a98cfa2bbb5ec4d3e869b5e51f2e06 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Feb 2009 23:28:53 +0000 Subject: [PATCH 011/112] I2PTunnel: First cut at SOCKS UDP (untested); also some streamr and UDP tweaks --- .../net/i2p/i2ptunnel/socks/MultiSink.java | 35 +++++++ .../net/i2p/i2ptunnel/socks/ReplyTracker.java | 36 +++++++ .../net/i2p/i2ptunnel/socks/SOCKS5Server.java | 84 +++++++++++++++-- .../net/i2p/i2ptunnel/socks/SOCKSHeader.java | 89 ++++++++++++++++++ .../net/i2p/i2ptunnel/socks/SOCKSUDPPort.java | 77 +++++++++++++++ .../i2p/i2ptunnel/socks/SOCKSUDPTunnel.java | 94 +++++++++++++++++++ .../i2ptunnel/socks/SOCKSUDPUnwrapper.java | 59 ++++++++++++ .../i2p/i2ptunnel/socks/SOCKSUDPWrapper.java | 49 ++++++++++ .../i2p/i2ptunnel/streamr/MultiSource.java | 4 + .../i2ptunnel/streamr/StreamrConsumer.java | 3 +- .../i2ptunnel/streamr/StreamrProducer.java | 5 +- .../src/net/i2p/i2ptunnel/udp/UDPSink.java | 21 +++-- .../src/net/i2p/i2ptunnel/udp/UDPSource.java | 14 ++- 13 files changed, 549 insertions(+), 21 deletions(-) create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java new file mode 100644 index 0000000000..3c63758c13 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java @@ -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 cache; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java new file mode 100644 index 0000000000..f6a124c951 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java @@ -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 cache; + private Sink sink; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java index 38c50f2661..5e52926074 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java @@ -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 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 */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java new file mode 100644 index 0000000000..763b9aa10a --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java @@ -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; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java new file mode 100644 index 0000000000..b56c9082ff --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java @@ -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; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java new file mode 100644 index 0000000000..0adaa19506 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java @@ -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 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 ports; + private Map cache; + private MultiSink demuxer; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java new file mode 100644 index 0000000000..2720b6fd47 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java @@ -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 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 cache; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java new file mode 100644 index 0000000000..4ec8361576 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java @@ -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 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 cache; +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java index 13d9b55202..5c5a08027e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java @@ -27,6 +27,10 @@ public class MultiSource implements Source, 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); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java index 3fc1d881b9..02b4434437 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java @@ -47,6 +47,7 @@ public class StreamrConsumer extends I2PTunnelUDPClientBase { public boolean close(boolean forced) { // send unsubscribe-message this.pinger.stop(); + this.sink.stop(); return super.close(forced); } @@ -59,6 +60,6 @@ public class StreamrConsumer extends I2PTunnelUDPClientBase { - private Sink sink; + private UDPSink sink; private Pinger pinger; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java index d722c5f95c..c3963b6a66 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java @@ -51,7 +51,8 @@ public class StreamrProducer extends I2PTunnelUDPServerBase { } public boolean close(boolean forced) { - // need some stop() methods in UDPSource and MultiSource + this.server.stop(); + this.multi.stop(); return super.close(forced); } @@ -65,6 +66,6 @@ public class StreamrProducer extends I2PTunnelUDPServerBase { private MultiSource multi; - private Source server; + private UDPSource server; private Sink subscriber; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java index 15feba6156..d2e8e89247 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java @@ -34,6 +34,8 @@ public class UDPSink implements Sink { } 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); @@ -46,17 +48,18 @@ public class UDPSink implements Sink { } } + public int getPort() { + return this.sock.getLocalPort(); + } + /** to pass to UDPSource constructor */ + public DatagramSocket getSocket() { + return this.sock; + } - - - - - - - - - + public void stop() { + this.sock.close(); + } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java index c54a984b0a..fc1dd5bf22 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java @@ -28,6 +28,13 @@ public class UDPSource implements Source, Runnable { // 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; @@ -57,13 +64,14 @@ public class UDPSource implements Source, Runnable { //System.out.print("i"); } catch(Exception e) { e.printStackTrace(); + break; } } } - - - + public void stop() { + this.sock.close(); + } From 0d2812db5063bc3c88044dbd480e05599e5f29b1 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Feb 2009 23:32:38 +0000 Subject: [PATCH 012/112] add standard logging to NativeBigInteger --- .../src/net/i2p/util/NativeBigInteger.java | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java index 7a64e24e42..970de52c85 100644 --- a/core/java/src/net/i2p/util/NativeBigInteger.java +++ b/core/java/src/net/i2p/util/NativeBigInteger.java @@ -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; + /** *

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); + } + /** *

Try loading it from an explictly build jbigi.dll / libjbigi.so first, before * looking into a jbigi.jar for any other libraries.

From 84bd8274ad627e376c6e2320849d95b64f4d491e Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 25 Feb 2009 00:05:30 +0000 Subject: [PATCH 013/112] * Router: Move addShutdownTask from Router to I2PAppContext so that apps can register more easily --- .../src/org/klomp/snark/SnarkManager.java | 5 ++-- .../net/i2p/router/web/ConfigNetHandler.java | 2 +- .../net/i2p/router/web/ConfigRestartBean.java | 8 +++--- .../i2p/router/web/ConfigServiceHandler.java | 12 ++++----- .../src/net/i2p/router/web/UpdateHandler.java | 2 +- core/java/src/net/i2p/I2PAppContext.java | 12 +++++++++ router/java/src/net/i2p/router/Router.java | 26 ++++++------------- 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 7b62ace843..54367af1ac 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -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) ) { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java index 0ddcd58a9d..a4fe7483ec 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java @@ -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); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java index 75a8108c50..e8eb6b26d3 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java @@ -25,20 +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.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.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(); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java index bd3bf7a5ea..8d3e5725ce 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java @@ -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(); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java index be39da2fd6..83495f33e1 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java @@ -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); } diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 6b3b0fd5bf..f26f74ab7b 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -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 _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 getShutdownTasks() { + return new HashSet(_shutdownTasks); + } + } diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 77e4b19681..13e801458c 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -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); } /** @@ -491,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); @@ -782,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; @@ -800,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); } From d222c7a9986dc7bfdfb9dc1159ae46df9388ba01 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 25 Feb 2009 01:18:38 +0000 Subject: [PATCH 014/112] move dest-to-hash conversion to new helper class --- .../i2p/router/web/ConfigKeyringHandler.java | 21 +---- core/java/src/net/i2p/util/ConvertToHash.java | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 core/java/src/net/i2p/util/ConvertToHash.java diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java index 09f0905bf3..b43bc4d1f1 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java @@ -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 { diff --git a/core/java/src/net/i2p/util/ConvertToHash.java b/core/java/src/net/i2p/util/ConvertToHash.java new file mode 100644 index 0000000000..0878556400 --- /dev/null +++ b/core/java/src/net/i2p/util/ConvertToHash.java @@ -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; + } +} From 56473c6b65bccf9b8d410141861ab20a1011c299 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 25 Feb 2009 02:00:13 +0000 Subject: [PATCH 015/112] add reverse lookup by hash --- .../client/naming/HostsTxtNamingService.java | 32 +++++++++++++++++++ .../net/i2p/client/naming/NamingService.java | 2 ++ 2 files changed, 34 insertions(+) diff --git a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java index 9fa227f817..054bd9d8f1 100644 --- a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java +++ b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java @@ -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; + } } diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java index 5b61b1bcf8..ee02ec9111 100644 --- a/core/java/src/net/i2p/client/naming/NamingService.java +++ b/core/java/src/net/i2p/client/naming/NamingService.java @@ -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 { * null 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 From 6648e182aef3909b8927e968640c4a43a663e915 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 26 Feb 2009 14:45:45 +0000 Subject: [PATCH 016/112] * I2CP Client: Add support for muxing --- .../net/i2p/client/streaming/PacketQueue.java | 12 +- .../src/net/i2p/client/I2PClientImpl.java | 2 +- core/java/src/net/i2p/client/I2PSession.java | 22 ++ .../i2p/client/I2PSessionDemultiplexer.java | 135 ++++++++ .../src/net/i2p/client/I2PSessionImpl.java | 8 +- .../src/net/i2p/client/I2PSessionImpl2.java | 29 +- .../net/i2p/client/I2PSessionListener.java | 4 +- .../net/i2p/client/I2PSessionMuxedImpl.java | 319 ++++++++++++++++++ .../i2p/client/I2PSessionMuxedListener.java | 62 ++++ 9 files changed, 581 insertions(+), 12 deletions(-) create mode 100644 core/java/src/net/i2p/client/I2PSessionDemultiplexer.java create mode 100644 core/java/src/net/i2p/client/I2PSessionMuxedImpl.java create mode 100644 core/java/src/net/i2p/client/I2PSessionMuxedListener.java diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java index a56e7753dd..e91cbdb7d4 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java @@ -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)) ) diff --git a/core/java/src/net/i2p/client/I2PClientImpl.java b/core/java/src/net/i2p/client/I2PClientImpl.java index 4783458a3a..5b1b44867d 100644 --- a/core/java/src/net/i2p/client/I2PClientImpl.java +++ b/core/java/src/net/i2p/client/I2PClientImpl.java @@ -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 } } diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java index d8c64f2222..1776af5c0f 100644 --- a/core/java/src/net/i2p/client/I2PSession.java +++ b/core/java/src/net/i2p/client/I2PSession.java @@ -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; } diff --git a/core/java/src/net/i2p/client/I2PSessionDemultiplexer.java b/core/java/src/net/i2p/client/I2PSessionDemultiplexer.java new file mode 100644 index 0000000000..9a1ff42e31 --- /dev/null +++ b/core/java/src/net/i2p/client/I2PSessionDemultiplexer.java @@ -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 _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); + } + } +} diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 00da88aa22..0e13f2c563 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -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 _availableMessages; + protected Map _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); } } diff --git a/core/java/src/net/i2p/client/I2PSessionImpl2.java b/core/java/src/net/i2p/client/I2PSessionImpl2.java index 56ef88974b..9abce4b72b 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl2.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl2.java @@ -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; diff --git a/core/java/src/net/i2p/client/I2PSessionListener.java b/core/java/src/net/i2p/client/I2PSessionListener.java index 4c78c65272..740ebeeab3 100644 --- a/core/java/src/net/i2p/client/I2PSessionListener.java +++ b/core/java/src/net/i2p/client/I2PSessionListener.java @@ -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); -} \ No newline at end of file +} diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java new file mode 100644 index 0000000000..1cd7b072a0 --- /dev/null +++ b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java @@ -0,0 +1,319 @@ +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, 0, 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, 0, 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 _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); + } +} diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedListener.java b/core/java/src/net/i2p/client/I2PSessionMuxedListener.java new file mode 100644 index 0000000000..118dc75cae --- /dev/null +++ b/core/java/src/net/i2p/client/I2PSessionMuxedListener.java @@ -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); +} From 3733b78ccf727b7dd4090b5b884d37c68285bfc2 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 27 Feb 2009 21:24:40 +0000 Subject: [PATCH 017/112] * I2PTunnelUDPClientBase: Fix client close, client target host * I2CP Mux: Fix UDP sends --- .../java/src/net/i2p/i2ptunnel/TunnelController.java | 9 ++++++++- .../net/i2p/i2ptunnel/streamr/StreamrConsumer.java | 1 + .../net/i2p/i2ptunnel/streamr/StreamrProducer.java | 1 + .../src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java | 3 ++- .../i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java | 12 ++---------- .../i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java | 3 +-- apps/i2ptunnel/jsp/editServer.jsp | 6 ++++-- .../java/src/net/i2p/client/I2PSessionMuxedImpl.java | 5 +++-- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 6c5fa4eb99..9cb3762ac9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -213,8 +213,15 @@ public class TunnelController implements Logging { _tunnel.runStreamrClient(new String[] { targetHost, targetPort, dest }, this); } - /** Streamr server is a UDP client, use the targetPort field for listenPort */ + /** + * 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); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java index 02b4434437..87ea0eefe6 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java @@ -42,6 +42,7 @@ public class StreamrConsumer extends I2PTunnelUDPClientBase { super.startRunning(); // send subscribe-message this.pinger.start(); + l.log("Streamr client ready"); } public boolean close(boolean forced) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java index c3963b6a66..b801cb94f4 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java @@ -48,6 +48,7 @@ public class StreamrProducer extends I2PTunnelUDPServerBase { public final void startRunning() { super.startRunning(); this.server.start(); + l.log("Streamr server ready"); } public boolean close(boolean forced) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java index 09385d46fe..58c5bfda49 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java @@ -27,7 +27,8 @@ public class I2PSinkAnywhere implements Sink { this.raw = raw; // create maker - this.maker = new I2PDatagramMaker(this.sess); + if (!raw) + this.maker = new I2PDatagramMaker(this.sess); } /** @param to - where it's going */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java index 0123be6eab..c92da6ae8a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java @@ -141,9 +141,6 @@ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements So } else { _i2pSink = new I2PSinkAnywhere(_session, false); } - - //configurePool(tunnel); - } /** @@ -165,13 +162,7 @@ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements So startRunning = true; startLock.notify(); } - - if (open && listenerReady) { - notifyEvent("openBaseClientResult", "ok"); - } else { - l.log("Error listening - please see the logs!"); - notifyEvent("openBaseClientResult", "error"); - } + open = true; } /** @@ -187,6 +178,7 @@ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements So } catch (I2PSessionException ise) {} } l.log("Closing client " + toString()); + open = false; return true; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java index fe129fb131..8dcd66a365 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java @@ -41,7 +41,7 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin private static volatile long __serverId = 0; - private Logging l; + protected Logging l; private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000; /** default timeout to 3 minutes - override if desired */ @@ -137,7 +137,6 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin start(); //} - l.log("Ready!"); notifyEvent("openServerResult", "ok"); open = true; } diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 0e9f9c0caa..70a9df9f72 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -88,14 +88,16 @@ <% } %> - <% if (!"streamrserver".equals(tunnelType)) { %>
- <% } // !streamrserver %>
-
- -
-
- - class="tickbox" /> -
-
- - class="tickbox" /> -
-
- - -
- -
-
-
-
+
+ +
+
+ + class="tickbox" /> +
+
+ + +
class="tickbox" /> + Enable + class="tickbox" /> + Disable +
+
+
+ + +
+ +
+
+
+ +
+ +
+
+ + class="tickbox" /> +
+
+ + +
+ +
+
+
+
- + (Tunnel must be stopped first)
From 59b624a4a4fb9fd5857b424c46faeab6bf6b16fe Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 1 Mar 2009 20:44:01 +0000 Subject: [PATCH 022/112] add reasonable privkey file name default --- apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index e68d140c5c..941edd0bda 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -60,8 +60,9 @@ public class EditBean extends IndexBean { TunnelController tun = getController(tunnel); if (tun != null && tun.getPrivKeyFile() != null) return tun.getPrivKeyFile(); - else - return ""; + if (tunnel < 0) + tunnel = _group.getControllers().size(); + return "i2ptunnel" + tunnel + "-privKeys.dat"; } public boolean startAutomatically(int tunnel) { From c455fa6309fb7c66e9159d99d9f0e162010cddfa Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 1 Mar 2009 20:45:16 +0000 Subject: [PATCH 023/112] * OCMOSJ: - Change from 5% reply requests to at least once per minute, in hopes of reducing IRC drops - More clean up of the cache cleaning --- .../OutboundClientMessageOneShotJob.java | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index 0e858ef779..20d69ea733 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -103,6 +103,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { private static final Object _initializeLock = new Object(); private static boolean _initialized = false; private static final int CLEAN_INTERVAL = 5*60*1000; + private static final int REPLY_REQUEST_INTERVAL = 60*1000; /** * Send the sucker @@ -212,7 +213,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * * Key the cache on the source+dest pair. */ - private static HashMap _leaseSetCache = new HashMap(); + private static HashMap _leaseSetCache = new HashMap(); private LeaseSet getReplyLeaseSet(boolean force) { LeaseSet newLS = getContext().netDb().lookupLeaseSetLocally(_from.calculateHash()); if (newLS == null) @@ -247,7 +248,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { long now = getContext().clock().now(); synchronized (_leaseSetCache) { if (!force) { - LeaseSet ls = (LeaseSet) _leaseSetCache.get(hashPair()); + LeaseSet ls = _leaseSetCache.get(hashPair()); if (ls != null) { if (ls.equals(newLS)) { // still good, send it 10% of the time @@ -312,7 +313,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * lease). * */ - private static HashMap _leaseCache = new HashMap(); + private static HashMap _leaseCache = new HashMap(); private boolean getNextLease() { _leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash()); if (_leaseSet == null) { @@ -325,7 +326,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { // Use the same lease if it's still good // Even if _leaseSet changed, _leaseSet.getEncryptionKey() didn't... synchronized (_leaseCache) { - _lease = (Lease) _leaseCache.get(hashPair()); + _lease = _leaseCache.get(hashPair()); if (_lease != null) { // if outbound tunnel length == 0 && lease.firsthop.isBacklogged() don't use it ?? if (!_lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) { @@ -446,6 +447,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl { } } + /** + * This cache is used to ensure that we request a reply every so often. + * Hopefully this allows the router to recognize a failed tunnel and switch, + * before upper layers like streaming lib fail, even for low-bandwidth + * connections like IRC. + */ + private static HashMap _lastReplyRequestCache = new HashMap(); + /** * Send the message to the specified tunnel by creating a new garlic message containing * the (already created) payload clove as well as a new delivery status message. This garlic @@ -456,18 +465,27 @@ public class OutboundClientMessageOneShotJob extends JobImpl { */ private void send() { if (_finished) return; - if (getContext().clock().now() >= _overallExpiration) { + long now = getContext().clock().now(); + if (now >= _overallExpiration) { dieFatal(); return; } int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey()); _outTunnel = selectOutboundTunnel(_to); + // boolean wantACK = _wantACK || existingTags <= 30 || getContext().random().nextInt(100) < 5; // what's the point of 5% random? possible improvements or replacements: - // - wantACK if we changed their inbound lease (getNextLease() sets _wantACK) - // - wantACK if we changed our outbound tunnel (selectOutboundTunnel() sets _wantACK) - // - wantACK if we haven't in last 1m (requires a new static cache probably) - boolean wantACK = _wantACK || existingTags <= 30 || getContext().random().nextInt(100) < 5; + // DONE (getNextLease() is called before this): wantACK if we changed their inbound lease (getNextLease() sets _wantACK) + // DONE (selectOutboundTunnel() moved above here): wantACK if we changed our outbound tunnel (selectOutboundTunnel() sets _wantACK) + // DONE (added new cache): wantACK if we haven't in last 1m (requires a new static cache probably) + boolean wantACK; + synchronized (_lastReplyRequestCache) { + Long lastSent = _lastReplyRequestCache.get(hashPair()); + wantACK = _wantACK || existingTags <= 30 || + lastSent == null || lastSent.longValue() < now - REPLY_REQUEST_INTERVAL; + if (wantACK) + _lastReplyRequestCache.put(hashPair(), Long.valueOf(now)); + } PublicKey key = _leaseSet.getEncryptionKey(); SessionKey sessKey = new SessionKey(); @@ -501,7 +519,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { // we dont receive the reply? hmm...) if (_log.shouldLog(Log.WARN)) _log.warn(getJobId() + ": Unable to create the garlic message (no tunnels left or too lagged) to " + _toString); - getContext().statManager().addRateData("client.dispatchNoTunnels", getContext().clock().now() - _start, 0); + getContext().statManager().addRateData("client.dispatchNoTunnels", now - _start, 0); dieFatal(); return; } @@ -539,12 +557,12 @@ public class OutboundClientMessageOneShotJob extends JobImpl { } else { if (_log.shouldLog(Log.WARN)) _log.warn(getJobId() + ": Could not find any outbound tunnels to send the payload through... this might take a while"); - getContext().statManager().addRateData("client.dispatchNoTunnels", getContext().clock().now() - _start, 0); + getContext().statManager().addRateData("client.dispatchNoTunnels", now - _start, 0); dieFatal(); } _clientMessage = null; _clove = null; - getContext().statManager().addRateData("client.dispatchPrepareTime", getContext().clock().now() - _start, 0); + getContext().statManager().addRateData("client.dispatchPrepareTime", now - _start, 0); if (!wantACK) getContext().statManager().addRateData("client.dispatchNoACK", 1, 0); } @@ -582,7 +600,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { /** * This is the place where we make I2P go fast. * - * We have four static caches. + * We have five static caches. * - The LeaseSet cache is used to decide whether to bundle our own leaseset, * which minimizes overhead. * - The Lease cache is used to persistently send to the same lease for the destination, @@ -590,6 +608,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * - The Tunnel and BackloggedTunnel caches are used to persistently use the same outbound tunnel * for the same destination, * which keeps the streaming lib happy by minimizing out-of-order delivery. + * - The last reply requested cache ensures that a reply is requested every so often, + * so that failed tunnels are recognized. * */ @@ -629,17 +649,17 @@ public class OutboundClientMessageOneShotJob extends JobImpl { } if (_lease != null) { synchronized(_leaseCache) { - Lease l = (Lease) _leaseCache.get(key); + Lease l = _leaseCache.get(key); if (l != null && l.equals(_lease)) _leaseCache.remove(key); } } if (_outTunnel != null) { synchronized(_tunnelCache) { - TunnelInfo t =(TunnelInfo) _backloggedTunnelCache.get(key); + TunnelInfo t = _backloggedTunnelCache.get(key); if (t != null && t.equals(_outTunnel)) _backloggedTunnelCache.remove(key); - t = (TunnelInfo) _tunnelCache.get(key); + t = _tunnelCache.get(key); if (t != null && t.equals(_outTunnel)) _tunnelCache.remove(key); } @@ -652,17 +672,12 @@ public class OutboundClientMessageOneShotJob extends JobImpl { */ 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(); String k = (String) entry.getKey(); LeaseSet l = (LeaseSet) entry.getValue(); if (l.getEarliestLeaseDate() < now) - deleteList.add(k); - } - for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) { - String k = (String) iter.next(); - tc.remove(k); + iter.remove(); } } @@ -671,17 +686,12 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * Caller must synchronize on 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(); String k = (String) entry.getKey(); Lease l = (Lease) entry.getValue(); if (l.isExpired(Router.CLOCK_FUDGE_FACTOR)) - deleteList.add(k); - } - for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) { - String k = (String) iter.next(); - tc.remove(k); + iter.remove(); } } @@ -690,17 +700,25 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * Caller must synchronize on 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 (!ctx.tunnelManager().isValidTunnel(sourceFromHashPair(k), tunnel)) - deleteList.add(k); + iter.remove(); } - for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) { - String k = (String) iter.next(); - tc.remove(k); + } + + /** + * Clean out old reply times + * Caller must synchronize on tc. + */ + private static void cleanReplyCache(RouterContext ctx, HashMap tc) { + long now = ctx.clock().now(); + for (Iterator iter = tc.values().iterator(); iter.hasNext(); ) { + Long l = (Long) iter.next(); + if (l.longValue() < now - CLEAN_INTERVAL) + iter.remove(); } } @@ -720,6 +738,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl { cleanTunnelCache(_ctx, _tunnelCache); cleanTunnelCache(_ctx, _backloggedTunnelCache); } + synchronized(_lastReplyRequestCache) { + cleanReplyCache(_ctx, _lastReplyRequestCache); + } } } @@ -731,8 +752,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * Key the caches on the source+dest pair. * */ - private static HashMap _tunnelCache = new HashMap(); - private static HashMap _backloggedTunnelCache = new HashMap(); + private static HashMap _tunnelCache = new HashMap(); + private static HashMap _backloggedTunnelCache = new HashMap(); private TunnelInfo selectOutboundTunnel(Destination to) { TunnelInfo tunnel; long now = getContext().clock().now(); @@ -743,7 +764,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * if you were the originator by backlogging the tunnel, then removing the * backlog and seeing if traffic came back or not. */ - tunnel = (TunnelInfo) _backloggedTunnelCache.get(hashPair()); + tunnel = _backloggedTunnelCache.get(hashPair()); if (tunnel != null) { if (getContext().tunnelManager().isValidTunnel(_from.calculateHash(), tunnel)) { if (!getContext().commSystem().isBacklogged(tunnel.getPeer(1))) { @@ -758,7 +779,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { _backloggedTunnelCache.remove(hashPair()); } // Use the same tunnel unless backlogged - tunnel = (TunnelInfo) _tunnelCache.get(hashPair()); + tunnel = _tunnelCache.get(hashPair()); if (tunnel != null) { if (getContext().tunnelManager().isValidTunnel(_from.calculateHash(), tunnel)) { if (tunnel.getLength() <= 1 || !getContext().commSystem().isBacklogged(tunnel.getPeer(1))) From 8f5257d5dc818dde614a9a414b1835a579bcf6bb Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 1 Mar 2009 23:14:38 +0000 Subject: [PATCH 024/112] make persistent client dests work --- .../java/src/net/i2p/i2ptunnel/I2PTunnel.java | 24 ++++++---- .../net/i2p/i2ptunnel/I2PTunnelClient.java | 4 +- .../i2p/i2ptunnel/I2PTunnelClientBase.java | 48 +++++++++++++++---- .../net/i2p/i2ptunnel/I2PTunnelIRCClient.java | 4 +- .../net/i2p/i2ptunnel/TunnelController.java | 16 +++++-- apps/i2ptunnel/jsp/editClient.jsp | 9 ++++ 6 files changed, 80 insertions(+), 25 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index dc9dfd2fcc..b11f954e7e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -552,14 +552,14 @@ public class I2PTunnel implements Logging, EventDispatcher { * Integer port number if the client is listening * sharedClient parameter is a String "true" or "false" * - * @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]} + * @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient [, privKeyFile]]} * @param l logger to receive events and output */ public void runClient(String args[], Logging l) { boolean isShared = true; - if (args.length == 3) + if (args.length >= 3) isShared = Boolean.valueOf(args[2].trim()).booleanValue(); - if ( (args.length == 2) || (args.length == 3) ) { + if (args.length >= 2) { int portNum = -1; try { portNum = Integer.parseInt(args[0]); @@ -572,7 +572,10 @@ public class I2PTunnel implements Logging, EventDispatcher { I2PTunnelTask task; ownDest = !isShared; try { - task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this); + String privateKeyFile = null; + if (args.length >= 4) + privateKeyFile = args[3]; + task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile); addtask(task); notifyEvent("clientTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { @@ -581,7 +584,7 @@ public class I2PTunnel implements Logging, EventDispatcher { notifyEvent("clientTaskId", Integer.valueOf(-1)); } } else { - l.log("client [,]|file:[ ]"); + l.log("client [,]|file:[ ] []"); l.log(" creates a client that forwards port to the pubkey.\n" + " use 0 as port to get a free port assigned. If you specify\n" + " a comma delimited list of pubkeys, it will rotate among them\n" @@ -720,11 +723,11 @@ public class I2PTunnel implements Logging, EventDispatcher { * Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started. * parameter sharedClient is a String, either "true" or "false" * - * @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]} + * @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient [, privKeyFile]]} * @param l logger to receive events and output */ public void runIrcClient(String args[], Logging l) { - if (args.length >= 2 && args.length <= 3) { + if (args.length >= 2) { int port = -1; try { port = Integer.parseInt(args[0]); @@ -751,7 +754,10 @@ public class I2PTunnel implements Logging, EventDispatcher { I2PTunnelTask task; ownDest = !isShared; try { - task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this); + String privateKeyFile = null; + if (args.length >= 4) + privateKeyFile = args[3]; + task = new I2PTunnelIRCClient(port, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile); addtask(task); notifyEvent("ircclientTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { @@ -760,7 +766,7 @@ public class I2PTunnel implements Logging, EventDispatcher { notifyEvent("ircclientTaskId", Integer.valueOf(-1)); } } else { - l.log("ircclient []"); + l.log("ircclient [ []]"); l.log(" creates a client that filter IRC protocol."); l.log(" (optional) indicates if this client shares tunnels with other clients (true of false)"); notifyEvent("ircclientTaskId", Integer.valueOf(-1)); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java index 4739a07f47..502bb28d5c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java @@ -31,8 +31,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase { */ public I2PTunnelClient(int localPort, String destinations, Logging l, boolean ownDest, EventDispatcher notifyThis, - I2PTunnel tunnel) throws IllegalArgumentException { - super(localPort, ownDest, l, notifyThis, "SynSender", tunnel); + I2PTunnel tunnel, String pkf) throws IllegalArgumentException { + super(localPort, ownDest, l, notifyThis, "SynSender", tunnel, pkf); if (waitEventValue("openBaseClientResult").equals("error")) { notifyEvent("openClientResult", "error"); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index c926156f42..fadbf9fa1c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -3,6 +3,7 @@ */ package net.i2p.i2ptunnel; +import java.io.FileInputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.net.ConnectException; @@ -59,6 +60,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna private byte[] pubkey; private String handlerName; + private String privKeyFile; private Object conLock = new Object(); @@ -91,18 +93,28 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna // I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null); //} + public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, + EventDispatcher notifyThis, String handlerName, + I2PTunnel tunnel) throws IllegalArgumentException { + this(localPort, ownDest, l, notifyThis, handlerName, tunnel, null); + } + /** + * @param privKeyFile null to generate a transient key + * * @throws IllegalArgumentException if the I2CP configuration is b0rked so * badly that we cant create a socketManager */ public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, - I2PTunnel tunnel) throws IllegalArgumentException{ + I2PTunnel tunnel, String pkf) throws IllegalArgumentException{ super(localPort + " (uninitialized)", notifyThis, tunnel); _clientId = ++__clientId; this.localPort = localPort; this.l = l; this.handlerName = handlerName + _clientId; + this.privKeyFile = pkf; + _context = tunnel.getContext(); _context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); @@ -195,28 +207,34 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna private static I2PSocketManager socketManager; protected synchronized I2PSocketManager getSocketManager() { - return getSocketManager(getTunnel()); + return getSocketManager(getTunnel(), this.privKeyFile); } protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) { + return getSocketManager(tunnel, null); + } + protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel, String pkf) { if (socketManager != null) { I2PSession s = socketManager.getSession(); if ( (s == null) || (s.isClosed()) ) { _log.info("Building a new socket manager since the old one closed [s=" + s + "]"); - socketManager = buildSocketManager(tunnel); + socketManager = buildSocketManager(tunnel, pkf); } else { _log.info("Not building a new socket manager since the old one is open [s=" + s + "]"); } } else { _log.info("Building a new socket manager since there is no other one"); - socketManager = buildSocketManager(tunnel); + socketManager = buildSocketManager(tunnel, pkf); } return socketManager; } protected I2PSocketManager buildSocketManager() { - return buildSocketManager(getTunnel()); + return buildSocketManager(getTunnel(), this.privKeyFile); } protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel) { + return buildSocketManager(tunnel, null); + } + protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf) { Properties props = new Properties(); props.putAll(tunnel.getClientOptions()); int portNum = 7654; @@ -230,10 +248,22 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna I2PSocketManager sockManager = null; while (sockManager == null) { - // if persistent dest - // sockManager = I2PSocketManagerFactory.createManager(privData, tunnel.host, portNum, props); - // else - sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props); + if (pkf != null) { + // Persistent client dest + FileInputStream fis = null; + try { + fis = new FileInputStream(pkf); + sockManager = I2PSocketManagerFactory.createManager(fis, tunnel.host, portNum, props); + } catch (IOException ioe) { + _log.error("Error opening key file", ioe); + // this is going to loop but if we break we'll get a NPE + } finally { + if (fis != null) + try { fis.close(); } catch (IOException ioe) {} + } + } else { + sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props); + } if (sockManager == null) { _log.log(Log.CRIT, "Unable to create socket manager"); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java index 5b223b1a42..732c222a71 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java @@ -39,12 +39,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable Logging l, boolean ownDest, EventDispatcher notifyThis, - I2PTunnel tunnel) throws IllegalArgumentException { + I2PTunnel tunnel, String pkf) throws IllegalArgumentException { super(localPort, ownDest, l, notifyThis, - "IRCHandler " + (++__clientId), tunnel); + "IRCHandler " + (++__clientId), tunnel, pkf); StringTokenizer tok = new StringTokenizer(destinations, ","); dests = new ArrayList(1); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 727b181581..419e5a899d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -192,7 +192,12 @@ public class TunnelController implements Logging { String listenPort = getListenPort(); String dest = getTargetDestination(); String sharedClient = getSharedClient(); - _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this); + if (getPersistentClientKey()) { + String privKeyFile = getPrivKeyFile(); + _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this); + } else { + _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this); + } } private void startSocksClient() { @@ -264,7 +269,12 @@ public class TunnelController implements Logging { String listenPort = getListenPort(); String dest = getTargetDestination(); String sharedClient = getSharedClient(); - _tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this); + if (getPersistentClientKey()) { + String privKeyFile = getPrivKeyFile(); + _tunnel.runClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this); + } else { + _tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this); + } } private void startServer() { @@ -395,7 +405,7 @@ public class TunnelController implements Logging { public String getProxyList() { return _config.getProperty("proxyList"); } public String getSharedClient() { return _config.getProperty("sharedClient", "true"); } public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); } - public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("persistentClientKey")).booleanValue(); } + public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("option.persistentClientKey")).booleanValue(); } public String getMyDestination() { if (_tunnel != null) { List sessions = _tunnel.getSessions(); diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 3c046baddc..178f564ef9 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -351,6 +351,7 @@
+ <% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { %>
+
+ + + (if known) +

+ <% } %>
- (if known) + <% if (!"".equals(editBean.getDestinationBase64(curTunnel))) { %> + Add to local addressbook + <% } %>
+
+ +
+
+ + class="tickbox" /> +
+ +
+
+
+ <% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { %>
@@ -329,7 +329,7 @@
<% } // end iterating over required groups for the current stat %>
diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index eb407ec16c..b3917cced2 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -326,7 +326,7 @@
@@ -354,7 +354,7 @@
From ca3b6eb00daf0ec644f8f4dba09b53d7f3a7a28e Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 13 Mar 2009 16:57:51 +0000 Subject: [PATCH 037/112] catch a reported NPE ? --- .../i2p/i2ptunnel/I2PTunnelHTTPServer.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 536844af9c..d6cb40a259 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -171,7 +171,24 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { sender.start(); browserout = _browser.getOutputStream(); - serverin = _webserver.getInputStream(); + // NPE seen here in 0.7-7, caused by addition of socket.close() in the + // catch (IOException ioe) block above in blockingHandle() ??? + // CRIT [ad-130280.hc] net.i2p.util.I2PThread : Killing thread Thread-130280.hc + // java.lang.NullPointerException + // at java.io.FileInputStream.(FileInputStream.java:131) + // at java.net.SocketInputStream.(SocketInputStream.java:44) + // at java.net.PlainSocketImpl.getInputStream(PlainSocketImpl.java:401) + // at java.net.Socket$2.run(Socket.java:779) + // at java.security.AccessController.doPrivileged(Native Method) + // at java.net.Socket.getInputStream(Socket.java:776) + // at net.i2p.i2ptunnel.I2PTunnelHTTPServer$CompressedRequestor.run(I2PTunnelHTTPServer.java:174) + // at java.lang.Thread.run(Thread.java:619) + // at net.i2p.util.I2PThread.run(I2PThread.java:71) + try { + serverin = _webserver.getInputStream(); + } catch (NullPointerException npe) { + throw new IOException("getInputStream NPE"); + } CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout); Sender s = new Sender(compressedOut, serverin, "server: server to browser"); if (_log.shouldLog(Log.INFO)) From cf02abd19cf15c00f725866846e486ae5f6afcd3 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 13 Mar 2009 16:58:23 +0000 Subject: [PATCH 038/112] allow .onion addresses for testing --- .../java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java index 2745cb0fa9..23ec70c3fd 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java @@ -198,7 +198,8 @@ public class SOCKS4aServer extends SOCKSServer { I2PSocket destSock; try { - if (connHostName.toLowerCase().endsWith(".i2p")) { + if (connHostName.toLowerCase().endsWith(".i2p") || + connHostName.toLowerCase().endsWith(".onion")) { _log.debug("connecting to " + connHostName + "..."); // Let's not due a new Dest for every request, huh? //I2PSocketManager sm = I2PSocketManagerFactory.createManager(); @@ -224,7 +225,7 @@ public class SOCKS4aServer extends SOCKSServer { } else { List proxies = t.getProxies(connPort); if (proxies == null || proxies.size() <= 0) { - String err = "No outproxy configured for port " + connPort + " and no default configured either"; + String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName; _log.error(err); try { sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); From 66eae60c48b1ad1958b68657f560dd1221521f60 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 14 Mar 2009 16:24:17 +0000 Subject: [PATCH 039/112] removed some hosts --- hosts.txt | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/hosts.txt b/hosts.txt index 95ca28af62..b30da63639 100644 --- a/hosts.txt +++ b/hosts.txt @@ -1,27 +1,6 @@ -tc.i2p=3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJcS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7gfqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teMd5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA -dyad.i2p=W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYppK9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HCVilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA -nightblade.i2p=nyErwSseXbsojcWtNkDyUul0YULtqr6qyWSzIp639Ygpe8juCdgPMLURVXcmlCvo~QPoHg6zt53KpgpGvB1-Wv2SGvc2Mvs~o8USw3ius8fP1URphqcBbulK8Ci0bgknt0kD0AfxqfMz-p~xk1QEMxq2kZEoB3oyIIFnQlpb2ByS74Lx8iKzXTrwWk19I3Dvu4nIq8CBDDwu3lYoCD2kC-jT5pjgglverGPEGN4o55LYVTtfSg4gAJFZeaE4KjBR5P1z7cca6UDjGMWfR0iCa8P3qpkY2ODYpk~8w2xgBbgDq-8Hzik~uraHc598ccS8QpwB0f0Jw~2PZcTjOPdZ-239U6p3tESXa7FXzRBCujv4Bx6CVFRhCmBHpyFnCD-MugZ~vR6XFSS2XBsCT~duXKq94HH2n1iAWslG4Vu44ut1JVhDPFzp~Dk7wujB0tCo2HXH2icRQxOWe37foU4LZSJ4oMpFDACBzwSfcZdIPsVRxGttKQx4yzgffR1Q~Jl7AAAA -bozo.i2p=ubMPUwY0op6B7Jr8SAjY2bQXze8m1sT6xF2N0cv43dIHwLTO0gUqn7FCP9jXZDodE9DR3fu8fG8x1Yz1SpXFk4WtFmuDuhdN7uaHuLIQ71PATC2GRhDS7NXqn7GsVZgQxhHKenaE5BKjIKt2amZ2~8CM0qBKTqwievUO-Y6zG~-8l~RpnAxDZUMOjKKy5R3~jEN9DFZCaKvXSNcOVFjZRGaD6d8NvkAJjndHdE3bFSJUDNv0qhhp09-mm~Se9C~FzjrAbhgappdNRiwQepXTWqRbjjt6lUPT2eJISPDxYxoeZkBGZa9XmfO9QH3hoMo0g~RbwLeBqtgeRGhVgFiC4pN8lFt3z7j8L-12575SUeOnJPIm3hQWXdTjKX1hqf4LopYBG84N95IeydPJegsmkIkAMzEb0d~-UZfVSP9yFgs37j~Fds5yxBsu-NFc6qmZihpXEd7jrfX1-HuJVmXFmwaZgyumRSRDbj714wxr7RP4Hb-liA3JrU-FbqNQFoTMAAAA duck.i2p=eFJdRYFmtjcpx9-Mw3JBdF6fwtb9cHRBR103Q8IGc91Jdfn0iYzK6Xx0oIzPvpbD4yOlPQm-C-7eTahrAkHa2FMCRTiiVq2a0nBp37W1uTvAToV-MKYPKTdFMxrXxvjS7qaSUXdJRcPaPexolfx-Gcjh~rN2tKCh0mz9beueiQ18~8qWGh6hUMb0yyA09ipL9vIkmHmooLwT9AZyzHXEzdLXZe1P~CG3L46QaXp9aTD7EkAwG6VBMjQrGiSJ-9FFhx4QcYAZWM-dfrtzbbVYfHxqQRBwzB27zLlaKVaqu4enC0N1cW1yy-cjnv0Wxokqe62B2uPzFYtloxQpBPfTLQZUfUzjskY-3Yg2AdSbEu37jYsnAJA95AlLz4t1W1vPTNiXzCaRqkkMX342SUkJK-HZE3wTjAgGPqJ9vMaC2YexPFViTxEO2Q0jDSjdPHG0D769qehkN5Bfb8MEI-yr3g9zLaY~w4r6T38ap33qfyISWlIJ5qCPcRVkeK8OqZ4vAAAA irc.duck.i2p=Bxqr5E7-56oJeya1lsDLBN6L1gKme-FUS6Bh~TQS3HswomK9rpjrYNeqBTBoE8TCFl161~FI3soWqbnmFhIdhskausZsO0ez5-4IXMJW8NTilWqXQ3OJxA20M9grohx3RjkZgXU1ooTx7wviSHtXQYiiqnzGnIzmmEZo5-Xx6VjXakctebWwbi2PrsE6XLxrxXBzB34l4KlVsyX504BJiOT6KXNaVZxvG61GfGVfNHdeXljMDE5d25UdFC6RJdDnJ3Z7Yb7EjAww78aowbR0VCfJDH~cB868-VOKIxmor3Rs7giaLXmUyW~GRtFX10COJj5V6BrhKs61XOXxfbyQKGVXZ0mM2A8cdZE1ftr96SZgGy~V8uUHKvoa1HpjMNrPL5Tr6EGfJxOxAy6PHwotn6a8UMnCZgEdTbQ6U3BTywU~x3SCAQvfOT~dl3sZ-5ujYWNFRp7RhdY-WHn1Kj59MfU-VpczGYdV3bRkwT5lpIjST~vopLfkYUeB6gcVSr49AAAA -jabber.duck.i2p=AzR8Zrms1sLXflvaZZ5CI2TS1cRvlO99D5Jh641-KS0lMSQPUnSApTfy9R4aPxZ3SWwpZPfSSGN-e7~kw8ucGQVB9EOHnht7WCz5UKC423vX4fFZqpTyqemDBx0CYXfhqRf-~7mjslXigR2nbQwh8NMl2bgLPknGe55wMlZPfsacS2WKNQYbdOvrgG6Zb6DK8RzTDrsum5h2jtIorv3CuMrNKLdfziXBDICep2Zp9jKDPBHgSsRgl6dW8A-5~WbkIPLpQwqeTzw90r8uJ9EIln~GlFulblJprCfLzxJ2LAFpxNcTkqzOJPElFfhjVrJOYbm6IfllBlHMVNbJjYTOsiJLqODxMiVt3pQx~AGFrx5mVtMUcz9cu9hVPnz2ZVmIDB~a4UGkexf~FCHhaQ6Emnr4nnZBiFSP3f4nASOMHLOs4SE3UKiYUB3ntmJssVdD1w-ZGrovHynkvXMjrJ9GhIeldFm0M5cOAhra0OtJagZ-bgphR0v99ADGPl7X5DoGAAAA -home.duck.i2p=dQmTRCAgcKlbt3VKdW-iFI4zd3WnDaM8ULsfIZb8oEdDuTq5Sl~1JguWH2Wl8PMRuxkhHpfOZ6IUJZtxo-rr3tfaDsYPl8SZSIbvGBs5QooLl2NNTPA~gQMJOHZkuM4KWMWYnFRWoaqAUt9KAmdyl5vgF20xv0rmSgRmm8t6c3QSTfNWSeSDYkRgEumaUKP-kRJnSKNeIOez1N2HQSlaDu~cH27E-VqEuAu8HppI87dV~v6nVKXR9-KgHzCdfHXxy2pxV9trJ8vmTEZO53ab11-BdDMoZjOv5sjMUADNDnhIW1RJ3c-ZcF9OotYsUrf7E1fRo3BwGg27LKhFnyBDmzI0a5uQJ9HCDlzSQ7yfHNMqNCtMfHgZE6Bw6F3tJfr5ISP1j5ibfj4k9tODyfWVqTionQfgrP5szHdyGu14Y4AtDW01-d9BNmBTHA53jCl~Jq6Qm5~CKcGZGS-05iyhmg9x1FtR-rW5Vf6o8uitSjtE5uPt~UHXacaGxQMJSy9vAAAA -nntp.duck.i2p=5SIb-1I6SVwFlW~XlYTNJU1NCpbyHJ3co8KdrpDxBosnHtdsZzq2qzYzGDfvkKK3WRoKmRGYCE77uXAvOQjEyWoTkkGeY2xl3B1t4t8K4j8dFvYtDJkRGePxGaY3MW-9ANGUQsGOhh6qq4lcQ7rv-AWyDftfZ3pGaHc79VukSo7-6OSh4WDw~0l~DjdFjQsZOVNTbKIfDxzSjKnkTpNc73nrLhRE5nmnMj7bljTzNtAHiVf~hFMndPxF80JZt6erLqy62-~XbevTWpn2KCTjYzhYUkwYW95-SW27ph5rIVZneizLEanSPtdUkDGhMEjjmy39Qr5rD5i9H-ioIP3NppRkjPwrtI9VJpsJtzv75uANIXy48RCBVXXA18ng8o7FVevdyjVb~C90IXpJF~mT7DI94rTT5QnzlpJVCid65kOoNzXFC80lP-iiwXMMBEcys8RGA9hdOvagkFDJ64l0GwrVpFGO2AVMsMenR0WHEvJOjNv62sT6rlntO3ZywhNXAAAA -pgp.duck.i2p=kRm2KRa2EiWO~XQFYxSg6UM9lXYHZ93IB80j3ShFhJOOZ4AN05BrTGjMeT9nhn5n1LMEUhy9HJuN-Lhkd89Mbhufk7di6M40wqySns2g0T7XUCFUgQ8kl4~BoY8M9U0pHpM3RJcCw8WJEFGEk~fX8tgC4XQB43UIXfrdKTlNfKhxZODE4UdvlFfFdOYMgH53x8UgMZUprn~URTRNg1uwcaXr2luMwts6HnDt1bDd8elitsWViOJiw42yAFMqBnf-7mhiTCsoYg-nsOiq0Jirt58cBjAGUL5ujZo~vfXkLslDKjHOP32Y7HIi5ANEsbbRr~8QrVUojnVoJwFXs8BQBTevRpGLkCSLnKa31jNSt3msOnEP-n729Jabwj8o3pdRk9e-3~~y9gfj4bcmpSH9sOJoqmipXDiqOvv0tL9twERqse3tAwjE1NgXTvl5e2Zc~F0xJ-L2aG~6BX175ihjjEiYGWRYaoEisHMZdMtivsAK92dKl1JVEkuDF3W8KjL8AAAA -scp.duck.i2p=AE2Ff7x-tJMI901UKEEXkcwb9~5KZKs--VBXEoZnnpC~mlgnqGsZr8MBvRvs6xAzP7xXLeL~cHQ~gvPFT60obEBitwH~JcKMahhJDGb0p23Z8B81QajXijypDpVfMDfFbMiqQGctXhnBidNKWe7HQEcJGZer-SCu3m8CiftcZ0t0g5Y67B2AtqLhza5xfepq6FQ-Tl6fpgS-UDcGUAqMpLUYfrBFB11oM09TRVsfNp~NIAvMdrvXiX8dTDUHJM4FRdpV2OsJiyDdgf5-W6s6ssxohviT5MdijUSYQw5sWj8~9Xkv~~aa11Dvty0E7K9IhXAeJlwBe3VuDizsAJGnFIU7PwZXV7-9-28Zgldarg1P-rh2QDvCqF1vjdyZe99BBcTiCqvE3zx2N-9eT~FeOURrgE~2rFBKVBZfuAASSORqiXIeAANKklCW2pGQrM3DkX8ybi93Weg~eBjwGQugO1FRZ-ISq7npRWruMiC7f~fWLqkmRUy9HahPNp8E41s9AAAA squid.i2p=8fiWZbRjOEzrj5n4jSqjN9UN54wTrsgEjqn7GRUQpLx1svf8lwckXPV5buP2VEYGo~83ftkIcDKyMLXkxSr8jqbb4yAEgPe2~w7OT~8LNvmVPz0xZhIO6fiw0WU4xD9x5PG1spYjWPnLFv7pynEvBpWFXaUlCacjWL2KkfViiGPXvveQqQIZs7VkxVD2HK-oT4yIjdqHpc7Y8nEV9xwds3-LX6to5p70jFe~kZJA2fjWHsSCm92TtPvoR3aTlL1VS3JUKpcH6BL5irsh-SKODEtDRCErPQI~j2SHzhcD6dMUsI7bm3AxivjFpSQHqyXLmLVdxECYsMET~nIHmuv8NYTHQQ0jM0XTQzwnQwEHjHRBd1~spR9uS2~LSnX4Pw~X1WTknJpPK1f4Cu1O44X4RYcLRCsxpEzytUBXA4BQizrbYgOJVGQa9-PNGxJeZsnNUZ3PxUi23Oh-c4jUaB0ZjyKTWJSpzj1GI6vc-gW-0ixGJ358TCSbKgqdBv~g~f7yAAAA -fillament.i2p=Pa50z7pU~ni5nWwUdaDZ5CJxG0fYjoarm9wlxnkgX~wHMX9RPgQAXz~r0Rr1Nadt2OA~dr9RMHswrMok0hutK3JZuFD707D7FjmWW2w979Ee9I3zxKyx9W5A2eE49PPT131NLa3uINXLXOYVA5frfDOmM75Dmvm533r8e2kloemJyj22HpvRiSXiQYgqYJGDMH3Hlnwk884eRkQu7P8DJL~hcuKpyY0FzLZtfxTNsdSavGjl7rKPMzJeP02-9TS5TkdHokZrstVM5Cn9ay1c8DQrMds7SPXJy13Ut34QRjb65JxRV0mrnY3teXewW6QFvFMXJCsf5C3i46t-9Fufy5D1H8cSd2Tx~Xl71MC5-1AJCcIS01Od23E9tFY3dU7IOSRhKC~FiAslyk3x-BnBSpKxbgl1w~LArBm5plNiCiUemJU88xYdn1UyukLer~yNrEHAWspckCRkXFwmUtPkaGNTvfwBSYns-skNHSd7MAUUoS-ewStBdmtnDgRkwSG9AAAA -eco.i2p=KhRG6BGxVPh-BUDDfIgy0570cppTdighytcaGVR0HzQo46tgRMBp9Shlpax5FQX4nLHn6qHQbdFFpFbAwe7CiDhURCVF9-CxYmPurGadxlMPHMjz9O3jHX0CQiv2iULsk4XPrYXF3PqBc4t1J6vVyBVO7uTUhDi0gF6sN1Ro-1GLcWcsoR8Kx-hb~Z4WqGD0QAROOBPHnSRSb236qVBkhFvSkfigfBq3jFgEsttadYJA9ZLSUj1XrFFRBjz~xkRra8kJQSZl5dbfg-eZMlL49h61U6Uta5n1~tL6sarmnl9CaTl2Qo27SKB1OmMLeZEteA5G0-~LiOjN0nxaKpwrCjKIOyvwbQy2QqE-GEb9m8SN8nC2bwYK9fH15pTMHY8GvPYGcUukbF6RhefwzkEJLZ~PaAECrZYuLsn9KE5C35uRnlWJiuJlJ25hG7da5tFMyDB95efzq5IMxPeI0pMigRfuVfRSaGDpNos6JxjfEIX8umk3jIJUPhz1d8gP4QgrAAAA -aum.i2p=09hSo56PTtkFLUEt1iUTO7zYTnO-B~ogsIsyyPWif6q1Iz4wz4JoBflAWZtedPmwGmH0nly4HYUS0gAADoUmBUnXwemmO6dxT-hPQkfEW-A7b3uEvYQWIN~kyFyg0Pa~FN6TaD9kGFttBN-GE4wxiHhXmWdzWNDVb0q5PVGnxMm6Jleik8xkd2Lgeexze8rIv8LCocAWx074USVkbCVQwoFi2P7EnjLq8odSz1cJAntbuCFeUZcjbslE3qmlcTFMCNCZXWKVzn7d5m4oszCQ83NidgekwxJ-S~iS6mBwIS0XKI--4iXiKXzzCFf0KtYfEWpvKCuqNJOcU8vQWAA2-i7~K28aLPzccDQn7acXWLKRTXF3tf0i6e-lSx-X6WTSWK-fuNitjAtKu~jqO10d~bCk7y~UPL-XwdH1XSTbk-Phhk7UoBTDiHY6zQHdD~iAzXER~9JXsJ4UoIrGFVabg7frzSt82CN7Ek5Li4AMz5gg3wq9H9HUa7xM5QfGIJpXAAAA -mp3.aum.i2p=vBOu1caCAajaL5WRMuwk4LwfXrlcn0WzA6iHUKV5ULhaBpJb9pR3SZpnQms2Ot2c5Fvu5I6Rp7WF6QRcyasAhUmC915ap~2~VG8KCDP0z3Quh1-eqGcmzErsIfXdh09j3CWuxN~fH84hd~KswqGudFkWtFTM9RcuQUGSC1efG7uF03uaDI-DKu7eb4VUV-hmpXb3Lqntgo5qSMBMmjyUND-f6RBoXnqM005mUZJpMoYfsBhnUEq37GG8u6P9T94nlMmtz9R3gNURpBJJKPlnEqCBN4mlE5rwspQ0ovxAlogVMhSCpQ4jr6cyWIbNx-nMzKGDj~hMQ0ndbVnWw3EDC3KsPnRnDv0yVz8Fc1YpoPwerHej7VnTupDKxc4T-j8XNA1dN8SfPmaKYEPfavlmy7HFAGcsbmeRZOq-PVvlDdrKNflug8Ysodd5XkDbh7y2k1pRDjwNBQ1EgDVAtL05-i9jerqekHkbFKJ6DlT76f06vj1R2v9qlQzAYjpcKbI3AAAA -ogg.aum.i2p=wR2ETKWn-mxsTurWwmSujvjpOiIjLg5TsldFUa4YFTgiRZdFIB-bXuK59shfnchlEgAZR0IR3~hH-O8bZ~j6wVBdZWq7bGTmyTxQ3MeYPdqK7wH7Jp147YUabFlqJkyI~DluwBDylJrIUyc2qw~ogJ67x-KyzIF7JLnoCC4E-T8Z0vmTAFWSa3XC-ncghrdZQCqEXaCMlG9PN~a7dcDq~qdWoNoyFcgLd0IQfE8JuJ1wSvmWUNEd9vkB2Zuu3EoSoDv4C53Fc0YhVACNug~VEEL-ZBGcCBcpVNud8dOMq-CbavkD5yKqHlvq~uzRi6BY5ajHI77uepJygkHcsm-8T0PXWXdc5ib4TtUI03tPkTar4Y2iVocY~oLk2jh7pQKZNioHJT4StWv9Pj8EWaVX4-emQB5kZOBwZItjo~EAGEoBT14NSM7CmKClgc6sg7fpvWF~-cNHkZsurBndni~~FKmUeWoO0FRQRF9Ao~C1DOt2V9oBbEW1~n6anjL5V~IyAAAA -fcp.entropy.i2p=jy0D13oJVmxSa1MltstV3FOfA5e2WAEEZJiYOJIZSUOcNnAkaR3ghE-AX4vuqyQPyOEUydpauD6cS8vfx4iZkb2U3ddlLcOU3YrFKdLrySpGtD~V126VO-9nOJFwDQOOaKAsiVGRKtMPLC64GkpU6TWSIhiVYWb7WmeAHXLLlR71DtgamAxEIlP3VhytxlS3vuvAoEH9ItsBwkv4N~7jec60WMQINl~c7uDDsuzKFY8wQlkHnLFQJCQ0VExfNYqK9nZ3x8TXNPmNKTMMQ1CUCowgwR783U7UAYqsxNrpkuWvTleadn7QcR9i2v4~L9zOeHd4nHBy8PAjO29g6nf6DIsYhg4c2HYnPYzktQ1NIElytmW83BhbXJXLgNBs1eI9gDaQmOiXi74FMgfg63IcXCYWeqCdwEzSouSphaXEHDcZZVTx7DE9R-1Bi4Dt~KvPOFsAoOqsjHCpHq1gS0u5HiL0hkSm1I1EMk4JBY0j4rM1nAt7e0ix~WiOz5jXlTVSAAAA -http.entropy.i2p=ON2Ud-B0-pJKbTR0Obpjp9wEG8grUpu55gEn5Mz3-dkVkPhHvHK6iLasr~P~Rf4kPPZvn-eK7z6rAVfsAytAJ9pcTH3lXERTjkd9FzVJJ0twbZSQ~XzX5d-24IPIMf00KegjnDkRJ82cRMKa-u4H-ayei~Y7xsSx64zC1eHv6qFxavtql3zRrS~du41~EHtpjqOtOo9Ea3lfFjhm2jUIJpYyVHqve3WbTfMBlguVALwGZIfenph7oQ1Hx~OnEtaviWuOEpupjm11LS9xqCNsccaEpJGvGt6ijxd9hrEuQZ5Ja~C0fNxf3xNtgRaUhakA8Xoo~jz8rCkV2vYQo58kj0E5xYrUQczomj8y-eDBZyq29BP8pfe2G1u3hpHA1z470LUeMPk8qVx8Cx8ZKmSK9XCvOl7WCnFS2~UUfzxbxSxPn9LfzxDZp05AVi9t~hJg-zkrL2n1wfEnScuUFapxarwK90rlAxNSnau-K61WfcXqyVMwDxl3leJOeVdHqhpbAAAA -www.mail.i2p=Y~V8YK2M-my6-Gw0lkrkJouxeqPuB03idp-4uT9pkIXCA5nki9m4YFfPObSPv0E7c2shBxwlUo-6beaRQ-7tCawJssDRc0C0PhRj12QUYYdtZP7JS8SQXy68gZIylY-wfyEXleIC4mYY5mSthhdUUfyo1lqzrdHc1NpjPBxRJcyMBFBGUeM7Of9E9M518jXpVl0bAmxSnr5dy7sgKAVNufzfqIBfEHnmL2ZYH78FoGnPybsV0F9~154emkmt89ZUbx0BuYvH3kT1zin8pSxKw1NqxvqYt7p8CElq1--U38rO9U5Y~kLB9f6F3RYJdkl28ANkvdgJUgqiHLVI5oPWATrJLAOokyGKhK4Xl4Bjp4SCuemxHwTOGyd-4Kl8cO41u3w1LksndX9stkV6U1X0gL9BeSIoa1997IgMLVbUiDMyCz7-cA0y2tc0EdQdlpc2y77nTdo7z23dMSJzWDXsrfmLhX7M24D70htLLc1dpwZ1BUEvM1uPqGfsBSrHdl-sAAAA -smtp.mail.i2p=gfSAYcvEsuU3oNGqeMpq1wZqH-whE1i~YCXEDwYzp8LrmukWsndvPER1~gF5QFrIp2RMXiietF3zEPtAJgevSG4ULxRU0s9MSAMXXlCACVhlf0m493J6kIYnkypOPC-Z8sulyF2kXM8BURLfSH57SS2uxLbx0hkc8j0kR9iys3ksxm5dgW-Rs7clAvLmmfASJkXZiU6DRhWbW84GbpAi9McE3ORhNLrWV5t1W4DXqzT0tzF2W0i8BEGns8XdOBQei9RAewzo5NRGPBmUl6ZKjEJ-2UtX189HPs7FcLknfsRxXhRcPQ1RombPezYCgcNhOgWY9owHq64mwGaDCnnpDSM01sdAuMlFfs1JJMoYNrILckjiHUNzV2XS8A0PIWdO4W0cT1EUs-V2a7Ocvg397HpR6Z4k-7fOrjs9yvpFsCPIEKYUD0mjr44N5pJIc61GGuNE~2ihQZGA3ju0OnUKTRZel3nK0rxl-qfFXsBsEB5vt-MTvKS73ZJdxUKWWzbWAAAA -pop.mail.i2p=aG6owmzirq7QZKYovpSVa4-WBLfI1uJ38cNmb6kkSkcS8A~JdoLWPj6eXieN8r3m7YJLxxyZhf3urmK9qJbiIPBp53M8bOSSkldpFz0NkQPWUWmYXiOrEsOBlugbJ8nNelDcOebqoieKBOTaF-WPJQil5C6RYdUy~PL50O6Qp-Hog1868zP26leYBBFiyzzWI3bOpOsgV~4bNXnqKQZeHXz1Ua2DkV-vDBpeamPzvNWQI6cNodf04PBKXK~TlduLZDK7v1yTt2LhPSBM5nE7ZRtS8KQIdh4o-nyYRmHjA~OQ70gowGpmsRqXHQxOpPro~C7w3gSe2N0zhqSHKstwoFJD-NmsBQ5opyOiKccATpWEQdAwmoICD6rw7TGG4XYXCtyTD2xxLffER0SEsJ-BJesKKhdm-qyEMAOQq00jatoEs9jBYoujFLFQMUgaDejRJdEHWkiGT~x4auosHGYavmrcm-0mdX0CWYgfjwVb~PORhBqHJ1G5IgRPjoZLuxiJAAAA -nm.i2p=UhUVbM972VwQgqS28SkGPtghCb~IpdpeMW7O9E7I3HtlB2I3XGbMeUAoya5RHsoG3TYxf~P6lA5IM5Z~mDZlcbZ~AG7255FE6Z3Jl9kfMArneou6AYaCfNBqNTRS-P7yX9s0Kss-vM63yBulxhS7CqmBKTZsXR27PNjJS0PMYsWBzciuy8UqUkE0YEhSLWSUYAXLP9FKs065CNjxsLumkkoF~MNUaBNEmbCrjpv5Ih9vrwz4XAJSE~S61qSMj6O-nvEPDVhTJJ5ymeoZnYMpIRt4r7FkCTH8vYSkXZkhqXUkLC11WPC33lw2wzh-irmIb5GQeab9o0-DuNQcvUnbK13Jxkq5XiilfK6kgKiPcEniqxMb-4paZAl8dj9Zp01LvhfjlS0c459Jv-gr7ZkjkX7hhTaEVvqwPyFgoVKnxQCitoZrK98WRKJwu7EQb-Kin2vzUsYfpGGI0aT68~gdr23oom2FsoZ~owVuur1h0bJr9mnCaMf6jaioQE7wezxgAAAA -mp3.tc.i2p=LRCWTiJouI6ohqtaVBNHWZe2ymjhtyj3z9KdeI2G2D9l0cYFsG0CRUVT5VPYOg~WykALRVBiL-2U24fbiQ28hhPdQgBMBDl9aQiZJM2hv~di0uVOdARhRSgCDgRQAWioAfpXeg6pyklzXU3TNLY4c2CRLe~9Y7wuLbK3lONsAApcxxKeHLrfGNkZZwJTKd7PcG78KAHU07E-TVNf4tQrOh8tSrHaMB1r7cQQv1Jl8mNUZWz4fGeNYEZ1wr04w74Em2~Z5K0VZ4mH5DhFGXc9ALYzZf6uOVzZKiUC0eOcdfGNdVUbIog2CYJGH69TgAX69d5vF~kEzvHSzX7RxUTt0y25Rlbi6rHSDF36xOfBrOUVnSPn5X~TdKLygz~zusYpRdGZwlsyOTKVTzJHKlU6Vp5Dofijj1bUwXDQ59XCpFUqEDA17nETiOO0H5jfieYBPS6Ke2cFTAhutSvaw~albCd1eV7RPqeGdw-vFfKoucDIEVUT40B5qalyFRKIxx3lAAAA mesh.firerabbit.i2p=BLt3pciNQcVIU-IGnTfSsrputh~b6drZpc1vH8qeA745XoE~nMCGtw8S7HGYX4uEbwk876nQRPV4qRwGtWWkWBs8BX9qX8NABoXFk4G-6NifB4TxEizC~ZAnXZ2uFs~nrqodhyCR8bBHJL0tzBYK3E36zc~SA-DKqQ9XSHWp7ScW7Z3cdQnYKma~x4u5eZmcS23uie3OIfOCk6pJOabtaE-YWRa1eUizhucI-ysm789GumjTo858vHR1mTQfrsPTqNri3yz0aIe~w06ifciq~UlNjVfx89lLEso1vmg8rfTQ-hwxS-qz-u3K5x5vtqdGp673vCvmEnQpU6GEycmkqoLCho9pNQzGbka-OVHg8fZqlFMeBfj2iLz~zlv170jvX6HTlMCNfBYnFqavs2RQJj7--dJ0g7JHReGMKL~TciQjxljrV5AoN-0afRzTZqtDg13PL4tltJm5U1~f-GcxlsjKLZAlv26LlZXsvTDU5plldsernv3fDcBev9UaKYCwAAAA chess.fillament.i2p=8xvXLwcBYu2MxqMAoG6DIahvkNAwBQs43kdZTg9SlouzC3JSQ25RHvspbrxWoGIVAlB~XCxVmBB6EolYEHoSZv2YCziOj4UVxEbVP7nHkh32-7Uc5T7xlcjk8-rsmZzdgz9NhxKVn2Uhp3xtcdVAiyG4mpXisvK-7NgHY-mXPNvj6goaH58roeDUZ5otJN7nCFmcRAUpaUk-nlQs8YfgSCCEsWKOWhsVnAaHwtqtDlpdTo1YKooACMRSa-DcV5W75Il80JEWBD79qpSAeONGAOMMPT~VEMwNNg001VG-UZbvyspZdxHaETw2yd7N9l3-mwI-sYcveTTnNXLWO8IjdgDLdUIP5qrIW6WS9XZIHRKqT2kpwEw7xsEgCA~qSNiZWeai8n6Zs0rLmdyeZeafVEEW9vr6CKcLZ5W7i1SMDqCKnzSbZdd2Hvpb9aWFf5foEjLt33n8w2KSaCUe4zmN~xuQMq2yiB-vQ9~5fgPmphlMxo3ca5MTCfbhshw5137YAAAA kaji.i2p=i-nivH~36AgabgzvS06oKHR~MLoy6NA0oSv8JuNJDLZB8FXEDzIiyzWISmw9XJm8I7QqZ1yFH8~xe84STCHHvMMIQQrfBmOUODLWbKZor~~KhiHdNLtfVZC5BpnXkBCJkklj~fMYSpWa0C~pVRrZl75RoGtBjDVP9P8hioTv5B6RC86g2ypBH5r093rY0wnzxSL8-ZuV3F~H48VYbqro8cRlbMmjx2oEsSHkDpQyjCMVkIYKaCijkSArqZTG~zX6op6Ng9CJwdrkjKSsbzjV6MLnE4aNv-jm2WaGGD5pR24h7e3ImDOGAr17tXRtmNX5ZEQ1udQp8qIhd8UMUumrnm962r8KJWK~9WNzcVeqDrIxaaxC7vcQmXxoPeEW2efbH0yKhVZ7OFu~I9cAapSe~aNWp9UK4URSpuJvOedt0axp3ORaaM-a5U7noW3Ao-HB83qfFEPU-6uUu16HNiPqCFMJiA0qODTOwHiyyx4HKQvbhjujh4mmknSbsuapdgR1AAAA From f70adf8da63afef2727a71026dedb30e0968454e Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 14 Mar 2009 20:25:50 +0000 Subject: [PATCH 040/112] disapproval of revision '3ae245c48c0f90b0e70cf800de354e012801f6cd' --- hosts.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/hosts.txt b/hosts.txt index b30da63639..95ca28af62 100644 --- a/hosts.txt +++ b/hosts.txt @@ -1,6 +1,27 @@ +tc.i2p=3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJcS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7gfqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teMd5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA +dyad.i2p=W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYppK9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HCVilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA +nightblade.i2p=nyErwSseXbsojcWtNkDyUul0YULtqr6qyWSzIp639Ygpe8juCdgPMLURVXcmlCvo~QPoHg6zt53KpgpGvB1-Wv2SGvc2Mvs~o8USw3ius8fP1URphqcBbulK8Ci0bgknt0kD0AfxqfMz-p~xk1QEMxq2kZEoB3oyIIFnQlpb2ByS74Lx8iKzXTrwWk19I3Dvu4nIq8CBDDwu3lYoCD2kC-jT5pjgglverGPEGN4o55LYVTtfSg4gAJFZeaE4KjBR5P1z7cca6UDjGMWfR0iCa8P3qpkY2ODYpk~8w2xgBbgDq-8Hzik~uraHc598ccS8QpwB0f0Jw~2PZcTjOPdZ-239U6p3tESXa7FXzRBCujv4Bx6CVFRhCmBHpyFnCD-MugZ~vR6XFSS2XBsCT~duXKq94HH2n1iAWslG4Vu44ut1JVhDPFzp~Dk7wujB0tCo2HXH2icRQxOWe37foU4LZSJ4oMpFDACBzwSfcZdIPsVRxGttKQx4yzgffR1Q~Jl7AAAA +bozo.i2p=ubMPUwY0op6B7Jr8SAjY2bQXze8m1sT6xF2N0cv43dIHwLTO0gUqn7FCP9jXZDodE9DR3fu8fG8x1Yz1SpXFk4WtFmuDuhdN7uaHuLIQ71PATC2GRhDS7NXqn7GsVZgQxhHKenaE5BKjIKt2amZ2~8CM0qBKTqwievUO-Y6zG~-8l~RpnAxDZUMOjKKy5R3~jEN9DFZCaKvXSNcOVFjZRGaD6d8NvkAJjndHdE3bFSJUDNv0qhhp09-mm~Se9C~FzjrAbhgappdNRiwQepXTWqRbjjt6lUPT2eJISPDxYxoeZkBGZa9XmfO9QH3hoMo0g~RbwLeBqtgeRGhVgFiC4pN8lFt3z7j8L-12575SUeOnJPIm3hQWXdTjKX1hqf4LopYBG84N95IeydPJegsmkIkAMzEb0d~-UZfVSP9yFgs37j~Fds5yxBsu-NFc6qmZihpXEd7jrfX1-HuJVmXFmwaZgyumRSRDbj714wxr7RP4Hb-liA3JrU-FbqNQFoTMAAAA duck.i2p=eFJdRYFmtjcpx9-Mw3JBdF6fwtb9cHRBR103Q8IGc91Jdfn0iYzK6Xx0oIzPvpbD4yOlPQm-C-7eTahrAkHa2FMCRTiiVq2a0nBp37W1uTvAToV-MKYPKTdFMxrXxvjS7qaSUXdJRcPaPexolfx-Gcjh~rN2tKCh0mz9beueiQ18~8qWGh6hUMb0yyA09ipL9vIkmHmooLwT9AZyzHXEzdLXZe1P~CG3L46QaXp9aTD7EkAwG6VBMjQrGiSJ-9FFhx4QcYAZWM-dfrtzbbVYfHxqQRBwzB27zLlaKVaqu4enC0N1cW1yy-cjnv0Wxokqe62B2uPzFYtloxQpBPfTLQZUfUzjskY-3Yg2AdSbEu37jYsnAJA95AlLz4t1W1vPTNiXzCaRqkkMX342SUkJK-HZE3wTjAgGPqJ9vMaC2YexPFViTxEO2Q0jDSjdPHG0D769qehkN5Bfb8MEI-yr3g9zLaY~w4r6T38ap33qfyISWlIJ5qCPcRVkeK8OqZ4vAAAA irc.duck.i2p=Bxqr5E7-56oJeya1lsDLBN6L1gKme-FUS6Bh~TQS3HswomK9rpjrYNeqBTBoE8TCFl161~FI3soWqbnmFhIdhskausZsO0ez5-4IXMJW8NTilWqXQ3OJxA20M9grohx3RjkZgXU1ooTx7wviSHtXQYiiqnzGnIzmmEZo5-Xx6VjXakctebWwbi2PrsE6XLxrxXBzB34l4KlVsyX504BJiOT6KXNaVZxvG61GfGVfNHdeXljMDE5d25UdFC6RJdDnJ3Z7Yb7EjAww78aowbR0VCfJDH~cB868-VOKIxmor3Rs7giaLXmUyW~GRtFX10COJj5V6BrhKs61XOXxfbyQKGVXZ0mM2A8cdZE1ftr96SZgGy~V8uUHKvoa1HpjMNrPL5Tr6EGfJxOxAy6PHwotn6a8UMnCZgEdTbQ6U3BTywU~x3SCAQvfOT~dl3sZ-5ujYWNFRp7RhdY-WHn1Kj59MfU-VpczGYdV3bRkwT5lpIjST~vopLfkYUeB6gcVSr49AAAA +jabber.duck.i2p=AzR8Zrms1sLXflvaZZ5CI2TS1cRvlO99D5Jh641-KS0lMSQPUnSApTfy9R4aPxZ3SWwpZPfSSGN-e7~kw8ucGQVB9EOHnht7WCz5UKC423vX4fFZqpTyqemDBx0CYXfhqRf-~7mjslXigR2nbQwh8NMl2bgLPknGe55wMlZPfsacS2WKNQYbdOvrgG6Zb6DK8RzTDrsum5h2jtIorv3CuMrNKLdfziXBDICep2Zp9jKDPBHgSsRgl6dW8A-5~WbkIPLpQwqeTzw90r8uJ9EIln~GlFulblJprCfLzxJ2LAFpxNcTkqzOJPElFfhjVrJOYbm6IfllBlHMVNbJjYTOsiJLqODxMiVt3pQx~AGFrx5mVtMUcz9cu9hVPnz2ZVmIDB~a4UGkexf~FCHhaQ6Emnr4nnZBiFSP3f4nASOMHLOs4SE3UKiYUB3ntmJssVdD1w-ZGrovHynkvXMjrJ9GhIeldFm0M5cOAhra0OtJagZ-bgphR0v99ADGPl7X5DoGAAAA +home.duck.i2p=dQmTRCAgcKlbt3VKdW-iFI4zd3WnDaM8ULsfIZb8oEdDuTq5Sl~1JguWH2Wl8PMRuxkhHpfOZ6IUJZtxo-rr3tfaDsYPl8SZSIbvGBs5QooLl2NNTPA~gQMJOHZkuM4KWMWYnFRWoaqAUt9KAmdyl5vgF20xv0rmSgRmm8t6c3QSTfNWSeSDYkRgEumaUKP-kRJnSKNeIOez1N2HQSlaDu~cH27E-VqEuAu8HppI87dV~v6nVKXR9-KgHzCdfHXxy2pxV9trJ8vmTEZO53ab11-BdDMoZjOv5sjMUADNDnhIW1RJ3c-ZcF9OotYsUrf7E1fRo3BwGg27LKhFnyBDmzI0a5uQJ9HCDlzSQ7yfHNMqNCtMfHgZE6Bw6F3tJfr5ISP1j5ibfj4k9tODyfWVqTionQfgrP5szHdyGu14Y4AtDW01-d9BNmBTHA53jCl~Jq6Qm5~CKcGZGS-05iyhmg9x1FtR-rW5Vf6o8uitSjtE5uPt~UHXacaGxQMJSy9vAAAA +nntp.duck.i2p=5SIb-1I6SVwFlW~XlYTNJU1NCpbyHJ3co8KdrpDxBosnHtdsZzq2qzYzGDfvkKK3WRoKmRGYCE77uXAvOQjEyWoTkkGeY2xl3B1t4t8K4j8dFvYtDJkRGePxGaY3MW-9ANGUQsGOhh6qq4lcQ7rv-AWyDftfZ3pGaHc79VukSo7-6OSh4WDw~0l~DjdFjQsZOVNTbKIfDxzSjKnkTpNc73nrLhRE5nmnMj7bljTzNtAHiVf~hFMndPxF80JZt6erLqy62-~XbevTWpn2KCTjYzhYUkwYW95-SW27ph5rIVZneizLEanSPtdUkDGhMEjjmy39Qr5rD5i9H-ioIP3NppRkjPwrtI9VJpsJtzv75uANIXy48RCBVXXA18ng8o7FVevdyjVb~C90IXpJF~mT7DI94rTT5QnzlpJVCid65kOoNzXFC80lP-iiwXMMBEcys8RGA9hdOvagkFDJ64l0GwrVpFGO2AVMsMenR0WHEvJOjNv62sT6rlntO3ZywhNXAAAA +pgp.duck.i2p=kRm2KRa2EiWO~XQFYxSg6UM9lXYHZ93IB80j3ShFhJOOZ4AN05BrTGjMeT9nhn5n1LMEUhy9HJuN-Lhkd89Mbhufk7di6M40wqySns2g0T7XUCFUgQ8kl4~BoY8M9U0pHpM3RJcCw8WJEFGEk~fX8tgC4XQB43UIXfrdKTlNfKhxZODE4UdvlFfFdOYMgH53x8UgMZUprn~URTRNg1uwcaXr2luMwts6HnDt1bDd8elitsWViOJiw42yAFMqBnf-7mhiTCsoYg-nsOiq0Jirt58cBjAGUL5ujZo~vfXkLslDKjHOP32Y7HIi5ANEsbbRr~8QrVUojnVoJwFXs8BQBTevRpGLkCSLnKa31jNSt3msOnEP-n729Jabwj8o3pdRk9e-3~~y9gfj4bcmpSH9sOJoqmipXDiqOvv0tL9twERqse3tAwjE1NgXTvl5e2Zc~F0xJ-L2aG~6BX175ihjjEiYGWRYaoEisHMZdMtivsAK92dKl1JVEkuDF3W8KjL8AAAA +scp.duck.i2p=AE2Ff7x-tJMI901UKEEXkcwb9~5KZKs--VBXEoZnnpC~mlgnqGsZr8MBvRvs6xAzP7xXLeL~cHQ~gvPFT60obEBitwH~JcKMahhJDGb0p23Z8B81QajXijypDpVfMDfFbMiqQGctXhnBidNKWe7HQEcJGZer-SCu3m8CiftcZ0t0g5Y67B2AtqLhza5xfepq6FQ-Tl6fpgS-UDcGUAqMpLUYfrBFB11oM09TRVsfNp~NIAvMdrvXiX8dTDUHJM4FRdpV2OsJiyDdgf5-W6s6ssxohviT5MdijUSYQw5sWj8~9Xkv~~aa11Dvty0E7K9IhXAeJlwBe3VuDizsAJGnFIU7PwZXV7-9-28Zgldarg1P-rh2QDvCqF1vjdyZe99BBcTiCqvE3zx2N-9eT~FeOURrgE~2rFBKVBZfuAASSORqiXIeAANKklCW2pGQrM3DkX8ybi93Weg~eBjwGQugO1FRZ-ISq7npRWruMiC7f~fWLqkmRUy9HahPNp8E41s9AAAA squid.i2p=8fiWZbRjOEzrj5n4jSqjN9UN54wTrsgEjqn7GRUQpLx1svf8lwckXPV5buP2VEYGo~83ftkIcDKyMLXkxSr8jqbb4yAEgPe2~w7OT~8LNvmVPz0xZhIO6fiw0WU4xD9x5PG1spYjWPnLFv7pynEvBpWFXaUlCacjWL2KkfViiGPXvveQqQIZs7VkxVD2HK-oT4yIjdqHpc7Y8nEV9xwds3-LX6to5p70jFe~kZJA2fjWHsSCm92TtPvoR3aTlL1VS3JUKpcH6BL5irsh-SKODEtDRCErPQI~j2SHzhcD6dMUsI7bm3AxivjFpSQHqyXLmLVdxECYsMET~nIHmuv8NYTHQQ0jM0XTQzwnQwEHjHRBd1~spR9uS2~LSnX4Pw~X1WTknJpPK1f4Cu1O44X4RYcLRCsxpEzytUBXA4BQizrbYgOJVGQa9-PNGxJeZsnNUZ3PxUi23Oh-c4jUaB0ZjyKTWJSpzj1GI6vc-gW-0ixGJ358TCSbKgqdBv~g~f7yAAAA +fillament.i2p=Pa50z7pU~ni5nWwUdaDZ5CJxG0fYjoarm9wlxnkgX~wHMX9RPgQAXz~r0Rr1Nadt2OA~dr9RMHswrMok0hutK3JZuFD707D7FjmWW2w979Ee9I3zxKyx9W5A2eE49PPT131NLa3uINXLXOYVA5frfDOmM75Dmvm533r8e2kloemJyj22HpvRiSXiQYgqYJGDMH3Hlnwk884eRkQu7P8DJL~hcuKpyY0FzLZtfxTNsdSavGjl7rKPMzJeP02-9TS5TkdHokZrstVM5Cn9ay1c8DQrMds7SPXJy13Ut34QRjb65JxRV0mrnY3teXewW6QFvFMXJCsf5C3i46t-9Fufy5D1H8cSd2Tx~Xl71MC5-1AJCcIS01Od23E9tFY3dU7IOSRhKC~FiAslyk3x-BnBSpKxbgl1w~LArBm5plNiCiUemJU88xYdn1UyukLer~yNrEHAWspckCRkXFwmUtPkaGNTvfwBSYns-skNHSd7MAUUoS-ewStBdmtnDgRkwSG9AAAA +eco.i2p=KhRG6BGxVPh-BUDDfIgy0570cppTdighytcaGVR0HzQo46tgRMBp9Shlpax5FQX4nLHn6qHQbdFFpFbAwe7CiDhURCVF9-CxYmPurGadxlMPHMjz9O3jHX0CQiv2iULsk4XPrYXF3PqBc4t1J6vVyBVO7uTUhDi0gF6sN1Ro-1GLcWcsoR8Kx-hb~Z4WqGD0QAROOBPHnSRSb236qVBkhFvSkfigfBq3jFgEsttadYJA9ZLSUj1XrFFRBjz~xkRra8kJQSZl5dbfg-eZMlL49h61U6Uta5n1~tL6sarmnl9CaTl2Qo27SKB1OmMLeZEteA5G0-~LiOjN0nxaKpwrCjKIOyvwbQy2QqE-GEb9m8SN8nC2bwYK9fH15pTMHY8GvPYGcUukbF6RhefwzkEJLZ~PaAECrZYuLsn9KE5C35uRnlWJiuJlJ25hG7da5tFMyDB95efzq5IMxPeI0pMigRfuVfRSaGDpNos6JxjfEIX8umk3jIJUPhz1d8gP4QgrAAAA +aum.i2p=09hSo56PTtkFLUEt1iUTO7zYTnO-B~ogsIsyyPWif6q1Iz4wz4JoBflAWZtedPmwGmH0nly4HYUS0gAADoUmBUnXwemmO6dxT-hPQkfEW-A7b3uEvYQWIN~kyFyg0Pa~FN6TaD9kGFttBN-GE4wxiHhXmWdzWNDVb0q5PVGnxMm6Jleik8xkd2Lgeexze8rIv8LCocAWx074USVkbCVQwoFi2P7EnjLq8odSz1cJAntbuCFeUZcjbslE3qmlcTFMCNCZXWKVzn7d5m4oszCQ83NidgekwxJ-S~iS6mBwIS0XKI--4iXiKXzzCFf0KtYfEWpvKCuqNJOcU8vQWAA2-i7~K28aLPzccDQn7acXWLKRTXF3tf0i6e-lSx-X6WTSWK-fuNitjAtKu~jqO10d~bCk7y~UPL-XwdH1XSTbk-Phhk7UoBTDiHY6zQHdD~iAzXER~9JXsJ4UoIrGFVabg7frzSt82CN7Ek5Li4AMz5gg3wq9H9HUa7xM5QfGIJpXAAAA +mp3.aum.i2p=vBOu1caCAajaL5WRMuwk4LwfXrlcn0WzA6iHUKV5ULhaBpJb9pR3SZpnQms2Ot2c5Fvu5I6Rp7WF6QRcyasAhUmC915ap~2~VG8KCDP0z3Quh1-eqGcmzErsIfXdh09j3CWuxN~fH84hd~KswqGudFkWtFTM9RcuQUGSC1efG7uF03uaDI-DKu7eb4VUV-hmpXb3Lqntgo5qSMBMmjyUND-f6RBoXnqM005mUZJpMoYfsBhnUEq37GG8u6P9T94nlMmtz9R3gNURpBJJKPlnEqCBN4mlE5rwspQ0ovxAlogVMhSCpQ4jr6cyWIbNx-nMzKGDj~hMQ0ndbVnWw3EDC3KsPnRnDv0yVz8Fc1YpoPwerHej7VnTupDKxc4T-j8XNA1dN8SfPmaKYEPfavlmy7HFAGcsbmeRZOq-PVvlDdrKNflug8Ysodd5XkDbh7y2k1pRDjwNBQ1EgDVAtL05-i9jerqekHkbFKJ6DlT76f06vj1R2v9qlQzAYjpcKbI3AAAA +ogg.aum.i2p=wR2ETKWn-mxsTurWwmSujvjpOiIjLg5TsldFUa4YFTgiRZdFIB-bXuK59shfnchlEgAZR0IR3~hH-O8bZ~j6wVBdZWq7bGTmyTxQ3MeYPdqK7wH7Jp147YUabFlqJkyI~DluwBDylJrIUyc2qw~ogJ67x-KyzIF7JLnoCC4E-T8Z0vmTAFWSa3XC-ncghrdZQCqEXaCMlG9PN~a7dcDq~qdWoNoyFcgLd0IQfE8JuJ1wSvmWUNEd9vkB2Zuu3EoSoDv4C53Fc0YhVACNug~VEEL-ZBGcCBcpVNud8dOMq-CbavkD5yKqHlvq~uzRi6BY5ajHI77uepJygkHcsm-8T0PXWXdc5ib4TtUI03tPkTar4Y2iVocY~oLk2jh7pQKZNioHJT4StWv9Pj8EWaVX4-emQB5kZOBwZItjo~EAGEoBT14NSM7CmKClgc6sg7fpvWF~-cNHkZsurBndni~~FKmUeWoO0FRQRF9Ao~C1DOt2V9oBbEW1~n6anjL5V~IyAAAA +fcp.entropy.i2p=jy0D13oJVmxSa1MltstV3FOfA5e2WAEEZJiYOJIZSUOcNnAkaR3ghE-AX4vuqyQPyOEUydpauD6cS8vfx4iZkb2U3ddlLcOU3YrFKdLrySpGtD~V126VO-9nOJFwDQOOaKAsiVGRKtMPLC64GkpU6TWSIhiVYWb7WmeAHXLLlR71DtgamAxEIlP3VhytxlS3vuvAoEH9ItsBwkv4N~7jec60WMQINl~c7uDDsuzKFY8wQlkHnLFQJCQ0VExfNYqK9nZ3x8TXNPmNKTMMQ1CUCowgwR783U7UAYqsxNrpkuWvTleadn7QcR9i2v4~L9zOeHd4nHBy8PAjO29g6nf6DIsYhg4c2HYnPYzktQ1NIElytmW83BhbXJXLgNBs1eI9gDaQmOiXi74FMgfg63IcXCYWeqCdwEzSouSphaXEHDcZZVTx7DE9R-1Bi4Dt~KvPOFsAoOqsjHCpHq1gS0u5HiL0hkSm1I1EMk4JBY0j4rM1nAt7e0ix~WiOz5jXlTVSAAAA +http.entropy.i2p=ON2Ud-B0-pJKbTR0Obpjp9wEG8grUpu55gEn5Mz3-dkVkPhHvHK6iLasr~P~Rf4kPPZvn-eK7z6rAVfsAytAJ9pcTH3lXERTjkd9FzVJJ0twbZSQ~XzX5d-24IPIMf00KegjnDkRJ82cRMKa-u4H-ayei~Y7xsSx64zC1eHv6qFxavtql3zRrS~du41~EHtpjqOtOo9Ea3lfFjhm2jUIJpYyVHqve3WbTfMBlguVALwGZIfenph7oQ1Hx~OnEtaviWuOEpupjm11LS9xqCNsccaEpJGvGt6ijxd9hrEuQZ5Ja~C0fNxf3xNtgRaUhakA8Xoo~jz8rCkV2vYQo58kj0E5xYrUQczomj8y-eDBZyq29BP8pfe2G1u3hpHA1z470LUeMPk8qVx8Cx8ZKmSK9XCvOl7WCnFS2~UUfzxbxSxPn9LfzxDZp05AVi9t~hJg-zkrL2n1wfEnScuUFapxarwK90rlAxNSnau-K61WfcXqyVMwDxl3leJOeVdHqhpbAAAA +www.mail.i2p=Y~V8YK2M-my6-Gw0lkrkJouxeqPuB03idp-4uT9pkIXCA5nki9m4YFfPObSPv0E7c2shBxwlUo-6beaRQ-7tCawJssDRc0C0PhRj12QUYYdtZP7JS8SQXy68gZIylY-wfyEXleIC4mYY5mSthhdUUfyo1lqzrdHc1NpjPBxRJcyMBFBGUeM7Of9E9M518jXpVl0bAmxSnr5dy7sgKAVNufzfqIBfEHnmL2ZYH78FoGnPybsV0F9~154emkmt89ZUbx0BuYvH3kT1zin8pSxKw1NqxvqYt7p8CElq1--U38rO9U5Y~kLB9f6F3RYJdkl28ANkvdgJUgqiHLVI5oPWATrJLAOokyGKhK4Xl4Bjp4SCuemxHwTOGyd-4Kl8cO41u3w1LksndX9stkV6U1X0gL9BeSIoa1997IgMLVbUiDMyCz7-cA0y2tc0EdQdlpc2y77nTdo7z23dMSJzWDXsrfmLhX7M24D70htLLc1dpwZ1BUEvM1uPqGfsBSrHdl-sAAAA +smtp.mail.i2p=gfSAYcvEsuU3oNGqeMpq1wZqH-whE1i~YCXEDwYzp8LrmukWsndvPER1~gF5QFrIp2RMXiietF3zEPtAJgevSG4ULxRU0s9MSAMXXlCACVhlf0m493J6kIYnkypOPC-Z8sulyF2kXM8BURLfSH57SS2uxLbx0hkc8j0kR9iys3ksxm5dgW-Rs7clAvLmmfASJkXZiU6DRhWbW84GbpAi9McE3ORhNLrWV5t1W4DXqzT0tzF2W0i8BEGns8XdOBQei9RAewzo5NRGPBmUl6ZKjEJ-2UtX189HPs7FcLknfsRxXhRcPQ1RombPezYCgcNhOgWY9owHq64mwGaDCnnpDSM01sdAuMlFfs1JJMoYNrILckjiHUNzV2XS8A0PIWdO4W0cT1EUs-V2a7Ocvg397HpR6Z4k-7fOrjs9yvpFsCPIEKYUD0mjr44N5pJIc61GGuNE~2ihQZGA3ju0OnUKTRZel3nK0rxl-qfFXsBsEB5vt-MTvKS73ZJdxUKWWzbWAAAA +pop.mail.i2p=aG6owmzirq7QZKYovpSVa4-WBLfI1uJ38cNmb6kkSkcS8A~JdoLWPj6eXieN8r3m7YJLxxyZhf3urmK9qJbiIPBp53M8bOSSkldpFz0NkQPWUWmYXiOrEsOBlugbJ8nNelDcOebqoieKBOTaF-WPJQil5C6RYdUy~PL50O6Qp-Hog1868zP26leYBBFiyzzWI3bOpOsgV~4bNXnqKQZeHXz1Ua2DkV-vDBpeamPzvNWQI6cNodf04PBKXK~TlduLZDK7v1yTt2LhPSBM5nE7ZRtS8KQIdh4o-nyYRmHjA~OQ70gowGpmsRqXHQxOpPro~C7w3gSe2N0zhqSHKstwoFJD-NmsBQ5opyOiKccATpWEQdAwmoICD6rw7TGG4XYXCtyTD2xxLffER0SEsJ-BJesKKhdm-qyEMAOQq00jatoEs9jBYoujFLFQMUgaDejRJdEHWkiGT~x4auosHGYavmrcm-0mdX0CWYgfjwVb~PORhBqHJ1G5IgRPjoZLuxiJAAAA +nm.i2p=UhUVbM972VwQgqS28SkGPtghCb~IpdpeMW7O9E7I3HtlB2I3XGbMeUAoya5RHsoG3TYxf~P6lA5IM5Z~mDZlcbZ~AG7255FE6Z3Jl9kfMArneou6AYaCfNBqNTRS-P7yX9s0Kss-vM63yBulxhS7CqmBKTZsXR27PNjJS0PMYsWBzciuy8UqUkE0YEhSLWSUYAXLP9FKs065CNjxsLumkkoF~MNUaBNEmbCrjpv5Ih9vrwz4XAJSE~S61qSMj6O-nvEPDVhTJJ5ymeoZnYMpIRt4r7FkCTH8vYSkXZkhqXUkLC11WPC33lw2wzh-irmIb5GQeab9o0-DuNQcvUnbK13Jxkq5XiilfK6kgKiPcEniqxMb-4paZAl8dj9Zp01LvhfjlS0c459Jv-gr7ZkjkX7hhTaEVvqwPyFgoVKnxQCitoZrK98WRKJwu7EQb-Kin2vzUsYfpGGI0aT68~gdr23oom2FsoZ~owVuur1h0bJr9mnCaMf6jaioQE7wezxgAAAA +mp3.tc.i2p=LRCWTiJouI6ohqtaVBNHWZe2ymjhtyj3z9KdeI2G2D9l0cYFsG0CRUVT5VPYOg~WykALRVBiL-2U24fbiQ28hhPdQgBMBDl9aQiZJM2hv~di0uVOdARhRSgCDgRQAWioAfpXeg6pyklzXU3TNLY4c2CRLe~9Y7wuLbK3lONsAApcxxKeHLrfGNkZZwJTKd7PcG78KAHU07E-TVNf4tQrOh8tSrHaMB1r7cQQv1Jl8mNUZWz4fGeNYEZ1wr04w74Em2~Z5K0VZ4mH5DhFGXc9ALYzZf6uOVzZKiUC0eOcdfGNdVUbIog2CYJGH69TgAX69d5vF~kEzvHSzX7RxUTt0y25Rlbi6rHSDF36xOfBrOUVnSPn5X~TdKLygz~zusYpRdGZwlsyOTKVTzJHKlU6Vp5Dofijj1bUwXDQ59XCpFUqEDA17nETiOO0H5jfieYBPS6Ke2cFTAhutSvaw~albCd1eV7RPqeGdw-vFfKoucDIEVUT40B5qalyFRKIxx3lAAAA mesh.firerabbit.i2p=BLt3pciNQcVIU-IGnTfSsrputh~b6drZpc1vH8qeA745XoE~nMCGtw8S7HGYX4uEbwk876nQRPV4qRwGtWWkWBs8BX9qX8NABoXFk4G-6NifB4TxEizC~ZAnXZ2uFs~nrqodhyCR8bBHJL0tzBYK3E36zc~SA-DKqQ9XSHWp7ScW7Z3cdQnYKma~x4u5eZmcS23uie3OIfOCk6pJOabtaE-YWRa1eUizhucI-ysm789GumjTo858vHR1mTQfrsPTqNri3yz0aIe~w06ifciq~UlNjVfx89lLEso1vmg8rfTQ-hwxS-qz-u3K5x5vtqdGp673vCvmEnQpU6GEycmkqoLCho9pNQzGbka-OVHg8fZqlFMeBfj2iLz~zlv170jvX6HTlMCNfBYnFqavs2RQJj7--dJ0g7JHReGMKL~TciQjxljrV5AoN-0afRzTZqtDg13PL4tltJm5U1~f-GcxlsjKLZAlv26LlZXsvTDU5plldsernv3fDcBev9UaKYCwAAAA chess.fillament.i2p=8xvXLwcBYu2MxqMAoG6DIahvkNAwBQs43kdZTg9SlouzC3JSQ25RHvspbrxWoGIVAlB~XCxVmBB6EolYEHoSZv2YCziOj4UVxEbVP7nHkh32-7Uc5T7xlcjk8-rsmZzdgz9NhxKVn2Uhp3xtcdVAiyG4mpXisvK-7NgHY-mXPNvj6goaH58roeDUZ5otJN7nCFmcRAUpaUk-nlQs8YfgSCCEsWKOWhsVnAaHwtqtDlpdTo1YKooACMRSa-DcV5W75Il80JEWBD79qpSAeONGAOMMPT~VEMwNNg001VG-UZbvyspZdxHaETw2yd7N9l3-mwI-sYcveTTnNXLWO8IjdgDLdUIP5qrIW6WS9XZIHRKqT2kpwEw7xsEgCA~qSNiZWeai8n6Zs0rLmdyeZeafVEEW9vr6CKcLZ5W7i1SMDqCKnzSbZdd2Hvpb9aWFf5foEjLt33n8w2KSaCUe4zmN~xuQMq2yiB-vQ9~5fgPmphlMxo3ca5MTCfbhshw5137YAAAA kaji.i2p=i-nivH~36AgabgzvS06oKHR~MLoy6NA0oSv8JuNJDLZB8FXEDzIiyzWISmw9XJm8I7QqZ1yFH8~xe84STCHHvMMIQQrfBmOUODLWbKZor~~KhiHdNLtfVZC5BpnXkBCJkklj~fMYSpWa0C~pVRrZl75RoGtBjDVP9P8hioTv5B6RC86g2ypBH5r093rY0wnzxSL8-ZuV3F~H48VYbqro8cRlbMmjx2oEsSHkDpQyjCMVkIYKaCijkSArqZTG~zX6op6Ng9CJwdrkjKSsbzjV6MLnE4aNv-jm2WaGGD5pR24h7e3ImDOGAr17tXRtmNX5ZEQ1udQp8qIhd8UMUumrnm962r8KJWK~9WNzcVeqDrIxaaxC7vcQmXxoPeEW2efbH0yKhVZ7OFu~I9cAapSe~aNWp9UK4URSpuJvOedt0axp3ORaaM-a5U7noW3Ao-HB83qfFEPU-6uUu16HNiPqCFMJiA0qODTOwHiyyx4HKQvbhjujh4mmknSbsuapdgR1AAAA From 33f4fac48f0d4afc53307c3c3926c2177810f735 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 14 Mar 2009 21:42:50 +0000 Subject: [PATCH 041/112] summary bar help --- apps/routerconsole/jsp/help.jsp | 116 +++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/apps/routerconsole/jsp/help.jsp b/apps/routerconsole/jsp/help.jsp index d53f93a885..279df95ddf 100644 --- a/apps/routerconsole/jsp/help.jsp +++ b/apps/routerconsole/jsp/help.jsp @@ -12,12 +12,126 @@

Help

-Sorry, there's no help text here yet, so check out the +Sorry, there's not much help text here yet, so also check out the FAQ on www.i2p2.i2p or the Deutsch FAQ. +You may also try the +forum +or IRC.
+

Summary Bar Information

+

General

+
    +
  • Ident: +The first four characters (24 bits) of your 44-character (256-bit) Base64 router hash. +The full hash is shown on your router info page. +Never reveal this to anyone, as your router info contains your IP. +
  • Version: +The version of the I2P software you are running. +
  • Now: +The current time (UTC) and the skew, if any. I2P requires your computer's time be accurate. +If the skew is more than a few seconds, please correct the problem by adjusting +your computer's time. +
  • Reachability: +The router's view of whether it can be contacted by other routers. +Further information is on the configuration page. +
+ +

Peers

+
    +
  • Active: +The first number is the number of peers you've sent or received a message from in the last few minutes. +This may range from 8-10 to several hundred, depending on your total bandwidth, +shared bandwidth, and locally-generated traffic. +The second number is the number of peers seen in the last hour or so. +Do not be concerned if these numbers vary widely. +
  • Fast: +This is the number of peers you use for building client tunnels. It is generally in the +range 8-15. Your fast peers are shown on the profiles page. +
  • High Capacity: +This is the number of peers you use for building some of your exploratory tunnels. It is generally in the +range 8-25. The fast peers are included in the high capacity tier. +Your high capacity peers are shown on the profiles page. +
  • Well Integrated: +This is the number of peers you use for network database inquiries. +These are usually the "floodfill" peers. +Your well integrated peers are shown on the bottom of the profiles page. +
  • Known: +This is the total number of routers you know about. +They are listed on the network database page. +This may range from under 100 to 1000 or more. +This number is not the total size of the network; +it may vary widely depending on your total bandwidth, +shared bandwidth, and locally-generated traffic. +I2P does not require a router to know every other router. +
+ +

Bandwidth in/out

+Should be self-explanatory. All values are in bytes per second, not bits per second. +Change your bandwidth limits on the configuration page. + +

Local destinations

+The local applications connecting through your router. +These may be clients started through I2PTunnel +or external programs connecting through SAM, BOB, or directly to I2CP. + +

Tunnels in/out

+The actual tunnels are shown on the the tunnels page. +
    +
  • Exploratory: +Tunnels built by your router and used for communication with the floodfill peers, +building new tunnels, and testing existing tunnels. +
  • Client: +Tunnels built by your router for each client's use. +
  • Participating: +Tunnels built by other routers through your router. +This may vary widely depending on network demand, your +shared bandwidth, and amount of locally-generated traffic. +The recommended method for limiting participating tunnels is +to change your share percentage on the configuration page. +You may also limit the total number by setting router.maxParticipatingTunnels=nnn on +the advanced configuration page. +
+ +

Congestion

+Some basic indications of router overload. +
  • Job lag: +How long jobs are waiting before execution. The job queue is listed on the jobs page. +Unfortunately, there are several other job queues in the router that may be congested, +and their status is not available in the router console. +The job lag should generally be zero. +If it is consistently higher than 500ms, your computer is very slow, or the +router has serious problems. +
  • Message delay: +How long an outbound message waits in the queue. +This should generally be a few hundred milliseconds or less. +If it is consistently higher than 1000ms, your computer is very slow, +or you should adjust your bandwidth limits, or your (bittorrent?) clients +may be sending too much data and should have their transmit bandwidth limit reduced. +
  • Tunnel lag: +This is the round trip time for a tunnel test, which sends a single message +out a client tunnel and in an exploratory tunnel, or vice versa. +It should usually be less than 5 seconds. +If it is consistently higher than that, your computer is very slow, +or you should adjust your bandwidth limits, or there are network problems. +
  • Handle backlog: +This is the number of pending requests from other routers to build a +participating tunnel through your router. +It should usually be close to zero. +If it is consistently high, your computer is too slow, +and you should reduce your share bandwidth limits. +
  • Accepting/Rejecting: +Your routers' status on accepting or rejecting +requests from other routers to build a +participating tunnel through your router. +Your router may accept all requests, accept or reject a percentage of requests, +or reject all requests for a number of reasons, to control +the bandwidth and CPU demands and maintain capacity for +local clients. + +

    Legal stuff

    The I2P router (router.jar) and SDK (i2p.jar) are almost entirely public domain, with a few notable exceptions:
      From 91b3889cbcba549891faeba9cad4bc90250175fe Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 15 Mar 2009 00:16:38 +0000 Subject: [PATCH 042/112] catch a rare AIOOB --- router/java/src/net/i2p/router/tunnel/FragmentHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java index 5a97956b9a..dbe256ebd5 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java @@ -74,6 +74,12 @@ public class FragmentHandler { int padding = 0; while (preprocessed[offset] != (byte)0x00) { offset++; // skip the padding + // AIOOBE http://forum.i2p/viewtopic.php?t=3187 + if (offset >= TrivialPreprocessor.PREPROCESSED_SIZE) { + _cache.release(new ByteArray(preprocessed)); + _context.statManager().addRateData("tunnel.corruptMessage", 1, 1); + return; + } padding++; } offset++; // skip the final 0x00, terminating the padding From d0a969ca33d78c1eb2e0794ab5c7e4351c7bb10b Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 16 Mar 2009 19:31:29 +0000 Subject: [PATCH 043/112] fix NPE on delayed open http://forum.i2p/viewtopic.php?t=3189 --- .../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index b42376dbf7..8e5ef9f4dc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -185,7 +185,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable /** * create the default options (using the default timeout, etc) - * + * unused? */ protected I2PSocketOptions getDefaultOptions() { Properties defaultOpts = getTunnel().getClientOptions(); @@ -210,6 +210,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT); if (!defaultOpts.contains("i2p.streaming.inactivityTimeout")) defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT); + // delayed start + if (sockMgr == null) + sockMgr = getSocketManager(); I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts); if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT)) opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT); From 0da964e47f620311b0721e7976ac69a971100370 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 16 Mar 2009 19:34:59 +0000 Subject: [PATCH 044/112] -9 --- history.txt | 8 ++++++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index 1388353e41..e5bfe64788 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,11 @@ +2009-03-16 zzz + * help.jsp: Add some + * I2PTunnel: Cleanup + * I2PTunnelHTTPClient: Fix NPE on delayed open + * I2PTunnelHTTPServer: Maybe catch an NPE + * SOCKS: Allow .onion addresses for onioncat testing + * Tunnel: Catch a rare AIOOB + 2009-03-09 zzz * Client: - Clean up retry code diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index eeea3faa97..6ade2ee810 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 8; + public final static long BUILD = 9; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From e5f19c98a8bde3ee448d46f369a35b8bd787f28d Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 15:21:34 +0000 Subject: [PATCH 045/112] change common corrupt errors to warns --- .../net/i2p/router/tunnel/FragmentHandler.java | 4 ++-- .../net/i2p/router/tunnel/FragmentedMessage.java | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java index dbe256ebd5..99b66c0c8b 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java @@ -393,8 +393,8 @@ public class FragmentHandler { _log.error("Error receiving fragmented message (corrupt?): " + stringified, ioe); } catch (I2NPMessageException ime) { if (stringified == null) stringified = msg.toString(); - if (_log.shouldLog(Log.ERROR)) - _log.error("Error receiving fragmented message (corrupt?): " + stringified, ime); + if (_log.shouldLog(Log.WARN)) + _log.warn("Error receiving fragmented message (corrupt?): " + stringified, ime); } } diff --git a/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java b/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java index d26c691b79..b0203f540d 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java @@ -78,13 +78,13 @@ public class FragmentedMessage { return false; } if (length <= 0) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Length is impossible (" + length + ") for messageId " + messageId); + if (_log.shouldLog(Log.WARN)) + _log.warn("Length is impossible (" + length + ") for messageId " + messageId); return false; } if (offset + length > payload.length) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Length is impossible (" + length + "/" + offset + " out of " + payload.length + ") for messageId " + messageId); + if (_log.shouldLog(Log.WARN)) + _log.warn("Length is impossible (" + length + "/" + offset + " out of " + payload.length + ") for messageId " + messageId); return false; } if (_log.shouldLog(Log.DEBUG)) @@ -131,13 +131,13 @@ public class FragmentedMessage { return false; } if (length <= 0) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Length is impossible (" + length + ") for messageId " + messageId); + if (_log.shouldLog(Log.WARN)) + _log.warn("Length is impossible (" + length + ") for messageId " + messageId); return false; } if (offset + length > payload.length) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Length is impossible (" + length + "/" + offset + " out of " + payload.length + ") for messageId " + messageId); + if (_log.shouldLog(Log.WARN)) + _log.warn("Length is impossible (" + length + "/" + offset + " out of " + payload.length + ") for messageId " + messageId); return false; } if (_log.shouldLog(Log.DEBUG)) From 09d700e1d6b6777d5fc2509dcc2b521855cdbe9d Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 18:19:47 +0000 Subject: [PATCH 046/112] fix encrypted leasesets --- core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java index 7a8bd200e1..e662f65727 100644 --- a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java @@ -79,7 +79,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { leaseSet.setEncryptionKey(li.getPublicKey()); leaseSet.setSigningKey(li.getSigningPublicKey()); - boolean encrypt = Boolean.valueOf(session.getOptions().getProperty("i2cp.encryptLeaseset")).booleanValue(); + boolean encrypt = Boolean.valueOf(session.getOptions().getProperty("i2cp.encryptLeaseSet")).booleanValue(); String sk = session.getOptions().getProperty("i2cp.leaseSetKey"); if (encrypt && sk != null) { SessionKey key = new SessionKey(); From 47edc3c8537f079ed49c97051f8f539fd3b31168 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 18:21:28 +0000 Subject: [PATCH 047/112] add warnings for some new features --- apps/i2ptunnel/jsp/editClient.jsp | 4 ++-- apps/i2ptunnel/jsp/editServer.jsp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 6dd839d979..915da5db96 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -318,7 +318,7 @@
      @@ -353,7 +353,7 @@
      diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index b3917cced2..59a7b9cf7e 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -286,7 +286,7 @@
      From e9063a22d565963db8435666cf6ebbaa7396eb9c Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 18:58:08 +0000 Subject: [PATCH 048/112] add anchors --- apps/routerconsole/jsp/configstats.jsp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/routerconsole/jsp/configstats.jsp b/apps/routerconsole/jsp/configstats.jsp index 6516361760..c7ec3090ca 100644 --- a/apps/routerconsole/jsp/configstats.jsp +++ b/apps/routerconsole/jsp/configstats.jsp @@ -88,6 +88,7 @@ function toggleAll(category)
  • LogGraph
    + checked="true" <% } %>/> From 6c365bef85b63c746cd09149c05a7d0510f8189c Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 19:52:06 +0000 Subject: [PATCH 049/112] add links to enable graphing --- apps/routerconsole/jsp/help.jsp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/routerconsole/jsp/help.jsp b/apps/routerconsole/jsp/help.jsp index 279df95ddf..68f1b62457 100644 --- a/apps/routerconsole/jsp/help.jsp +++ b/apps/routerconsole/jsp/help.jsp @@ -22,6 +22,10 @@ or IRC.

    Summary Bar Information

    +Many of the stats on the summary bar may be +configured to be +graphed for further analysis. +

    General

    • Ident: @@ -47,13 +51,16 @@ This may range from 8-10 to several hundred, depending on your total bandwidth, shared bandwidth, and locally-generated traffic. The second number is the number of peers seen in the last hour or so. Do not be concerned if these numbers vary widely. +Enable graphing
    • Fast: This is the number of peers you use for building client tunnels. It is generally in the range 8-15. Your fast peers are shown on the profiles page. +Enable graphing
    • High Capacity: This is the number of peers you use for building some of your exploratory tunnels. It is generally in the range 8-25. The fast peers are included in the high capacity tier. Your high capacity peers are shown on the profiles page. +Enable graphing
    • Well Integrated: This is the number of peers you use for network database inquiries. These are usually the "floodfill" peers. @@ -71,6 +78,7 @@ I2P does not require a router to know every other router.

      Bandwidth in/out

      Should be self-explanatory. All values are in bytes per second, not bits per second. Change your bandwidth limits on the configuration page. +Bandwidth is graphed by default.

      Local destinations

      The local applications connecting through your router. @@ -93,10 +101,12 @@ The recommended method for limiting participating tunnels is to change your share percentage on the configuration page. You may also limit the total number by setting router.maxParticipatingTunnels=nnn on the advanced configuration page. +Enable graphing

    Congestion

    Some basic indications of router overload. +
    • Job lag: How long jobs are waiting before execution. The job queue is listed on the jobs page. Unfortunately, there are several other job queues in the router that may be congested, @@ -104,18 +114,21 @@ and their status is not available in the router console. The job lag should generally be zero. If it is consistently higher than 500ms, your computer is very slow, or the router has serious problems. +Enable graphing
    • Message delay: How long an outbound message waits in the queue. This should generally be a few hundred milliseconds or less. If it is consistently higher than 1000ms, your computer is very slow, or you should adjust your bandwidth limits, or your (bittorrent?) clients may be sending too much data and should have their transmit bandwidth limit reduced. +Enable graphing (transport.sendProcessingTime)
    • Tunnel lag: This is the round trip time for a tunnel test, which sends a single message out a client tunnel and in an exploratory tunnel, or vice versa. It should usually be less than 5 seconds. If it is consistently higher than that, your computer is very slow, or you should adjust your bandwidth limits, or there are network problems. +Enable graphing (tunnel.testSuccessTime)
    • Handle backlog: This is the number of pending requests from other routers to build a participating tunnel through your router. From bb51bf49b03d35836630923ddee0c1e69c1059d4 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 20:24:20 +0000 Subject: [PATCH 050/112] - Suppress log error on manual stop - Prevent NPE when closing a delayed-open tunnel --- .../i2p/i2ptunnel/I2PTunnelClientBase.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index 94bc959c02..b6eb392243 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -375,7 +375,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna // This will build a new socket manager and a new dest if the session is closed. sockMgr = getSocketManager(); if (oldSockMgr != sockMgr) { - _log.error("Built a new destination on resume"); + _log.warn("Built a new destination on resume"); } } } // else the old socket manager will reconnect the old session if necessary @@ -431,8 +431,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna _context.statManager().addRateData("i2ptunnel.client.manageTime", total, total); } } catch (IOException ex) { - _log.error("Error listening for connections on " + localPort, ex); - notifyEvent("openBaseClientResult", "error"); + if (open) { + _log.error("Error listening for connections on " + localPort, ex); + notifyEvent("openBaseClientResult", "error"); + } synchronized (sockLock) { mySockets.clear(); } @@ -513,20 +515,23 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna // might risk to create an orphan socket. Would be better // to return with an error in that situation quickly. synchronized (sockLock) { - mySockets.retainAll(sockMgr.listSockets()); - if (!forced && mySockets.size() != 0) { - l.log("There are still active connections!"); - _log.debug("can't close: there are still active connections!"); - for (Iterator it = mySockets.iterator(); it.hasNext();) { - l.log("->" + it.next()); + if (sockMgr != null) { + mySockets.retainAll(sockMgr.listSockets()); + if (!forced && mySockets.size() != 0) { + l.log("There are still active connections!"); + _log.debug("can't close: there are still active connections!"); + for (Iterator it = mySockets.iterator(); it.hasNext();) { + l.log("->" + it.next()); + } + return false; + } + I2PSession session = sockMgr.getSession(); + if (session != null) { + getTunnel().removeSession(session); } - return false; - } - I2PSession session = sockMgr.getSession(); - if (session != null) { - getTunnel().removeSession(session); } l.log("Closing client " + toString()); + open = false; try { if (ss != null) ss.close(); } catch (IOException ex) { @@ -534,7 +539,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna return false; } l.log("Client closed."); - open = false; } synchronized (_waitingSockets) { _waitingSockets.notifyAll(); } From 41718b47c16c0d51769a49645e850c75aa252b50 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 21:29:15 +0000 Subject: [PATCH 051/112] increase default bw to 64/32 --- .../i2p/router/transport/FIFOBandwidthRefiller.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java index d4bdfea85f..693df45b18 100644 --- a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java +++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java @@ -33,11 +33,11 @@ public class FIFOBandwidthRefiller implements Runnable { public static final String PROP_OUTBOUND_BANDWIDTH_PEAK = "i2np.bandwidth.outboundBurstKBytes"; //public static final String PROP_REPLENISH_FREQUENCY = "i2np.bandwidth.replenishFrequencyMs"; - // no longer allow unlimited bandwidth - the user must specify a value, and if they do not, it is 32/16KBps - public static final int DEFAULT_INBOUND_BANDWIDTH = 48; - public static final int DEFAULT_OUTBOUND_BANDWIDTH = 24; - public static final int DEFAULT_INBOUND_BURST_BANDWIDTH = 64; - public static final int DEFAULT_OUTBOUND_BURST_BANDWIDTH = 32; + // no longer allow unlimited bandwidth - the user must specify a value, else use defaults below (KBps) + public static final int DEFAULT_INBOUND_BANDWIDTH = 64; + public static final int DEFAULT_OUTBOUND_BANDWIDTH = 32; + public static final int DEFAULT_INBOUND_BURST_BANDWIDTH = 80; + public static final int DEFAULT_OUTBOUND_BURST_BANDWIDTH = 40; public static final int DEFAULT_BURST_SECONDS = 60; From 2695461bd48a0da1ca056e9eda4dc803201cb0b1 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Mar 2009 21:31:55 +0000 Subject: [PATCH 052/112] -10 --- history.txt | 10 ++++++++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index e5bfe64788..d3fcf2f4ca 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,13 @@ +2009-03-24 zzz + * I2PTunnel: + - Add some warnings about new features + - Fix encrypted leasesets broken in about -4 + - Suppress log error on manual stop + - Fix NPE on close of a tunnel not open yet + * Transport: + - Increase default bw to 64/32, burst 80/40 + * Tunnels: Change some fragmentation errors to warns + 2009-03-16 zzz * help.jsp: Add some * I2PTunnel: Cleanup diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 6ade2ee810..c1d6bdf7c6 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 9; + public final static long BUILD = 10; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 5414d41de4d199e44da68a3b5b4376d792fc47ff Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 25 Mar 2009 23:19:37 +0000 Subject: [PATCH 053/112] I2PSnark: Use new BW Limits message, remove router.jar dependencies --- apps/i2psnark/java/build.xml | 3 +- .../java/src/org/klomp/snark/BWLimits.java | 44 +++++++++++++++++++ .../java/src/org/klomp/snark/Snark.java | 5 +-- .../src/org/klomp/snark/SnarkManager.java | 23 +++++----- core/java/src/net/i2p/client/I2PSession.java | 5 +++ .../src/net/i2p/client/I2PSessionImpl.java | 4 ++ .../src/net/i2p/client/I2PSimpleSession.java | 2 +- .../client/ClientMessageEventListener.java | 6 +-- 8 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 apps/i2psnark/java/src/org/klomp/snark/BWLimits.java diff --git a/apps/i2psnark/java/build.xml b/apps/i2psnark/java/build.xml index 02eee0aabc..9689fda068 100644 --- a/apps/i2psnark/java/build.xml +++ b/apps/i2psnark/java/build.xml @@ -18,7 +18,6 @@ - @@ -32,7 +31,7 @@ srcdir="./src" debug="true" deprecation="on" source="1.5" target="1.5" destdir="./build/obj" - classpath="../../../core/java/build/i2p.jar:../../../router/java/build/router.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" /> + classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" /> diff --git a/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java b/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java new file mode 100644 index 0000000000..eb157cb5e4 --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java @@ -0,0 +1,44 @@ +/* + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + */ +package org.klomp.snark; + +import java.util.Arrays; +import java.util.Properties; + +import net.i2p.I2PAppContext; +import net.i2p.client.I2PSessionException; +import net.i2p.client.I2PClient; +import net.i2p.client.I2PSession; +import net.i2p.client.I2PSimpleClient; + +/** + * Connect via I2CP and ask the router the bandwidth limits. + * + * The call is blocking and returns null on failure. + * Timeout is set to 5 seconds in I2PSimpleSession but it should be much faster. + * + * @author zzz + */ +class BWLimits { + + public static int[] getBWLimits(String host, int port) { + int[] rv = null; + try { + I2PClient client = new I2PSimpleClient(); + Properties opts = new Properties(); + opts.put(I2PClient.PROP_TCP_HOST, host); + opts.put(I2PClient.PROP_TCP_PORT, "" + port); + I2PSession session = client.createSession(null, opts); + session.connect(); + rv = session.bandwidthLimits(); + session.destroySession(); + } catch (I2PSessionException ise) {} + return rv; + } + + public static void main(String args[]) { + System.out.println(Arrays.toString(getBWLimits("127.0.0.1", 7654))); + } +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index e124955cfc..53c42dfadf 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -36,7 +36,6 @@ import java.util.Timer; import java.util.TimerTask; import net.i2p.I2PAppContext; -import net.i2p.router.client.ClientManagerFacadeImpl; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.data.Destination; import net.i2p.util.I2PThread; @@ -261,9 +260,9 @@ public class Snark public Snark(I2PAppContext ctx, Properties opts, String torrent, StorageListener slistener, boolean start, String rootDir) { this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir); - String host = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST); + String host = opts.getProperty("i2cp.hostname"); int port = 0; - String s = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_PORT); + String s = opts.getProperty("i2cp.port"); if (s != null) { try { port = Integer.parseInt(s); diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 54367af1ac..01a93a58c1 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -18,7 +18,6 @@ import java.util.TreeMap; import net.i2p.I2PAppContext; import net.i2p.data.Base64; import net.i2p.data.DataHelper; -import net.i2p.router.RouterContext; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; @@ -147,22 +146,21 @@ public class SnarkManager implements Snark.CompleteListener { _config.setProperty(PROP_EEP_PORT, "4444"); if (!_config.containsKey(PROP_UPLOADERS_TOTAL)) _config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS); - if (!_config.containsKey(PROP_UPBW_MAX)) { - try { - if (_context instanceof RouterContext) - _config.setProperty(PROP_UPBW_MAX, "" + (((RouterContext)_context).bandwidthLimiter().getOutboundKBytesPerSecond() / 2)); - else - _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW); - } catch (NoClassDefFoundError ncdfe) { - _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW); - } - } if (!_config.containsKey(PROP_DIR)) _config.setProperty(PROP_DIR, "i2psnark"); if (!_config.containsKey(PROP_AUTO_START)) _config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START); updateConfig(); } + + /** call from DirMonitor since loadConfig() is called before router I2CP is up */ + private void getBWLimit() { + if (!_config.containsKey(PROP_UPBW_MAX)) { + int[] limits = BWLimits.getBWLimits(_util.getI2CPHost(), _util.getI2CPPort()); + if (limits != null && limits[1] > 0) + _util.setMaxUpBW(limits[1]); + } + } private void updateConfig() { String i2cpHost = _config.getProperty(PROP_I2CP_HOST); @@ -619,6 +617,9 @@ public class SnarkManager implements Snark.CompleteListener { _messages.remove(0); } + // here because we need to delay until I2CP is up + // although the user will see the default until then + getBWLimit(); while (true) { File dir = getDataDir(); _log.debug("Directory Monitor loop over " + dir.getAbsolutePath()); diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java index 1776af5c0f..1998dad55a 100644 --- a/core/java/src/net/i2p/client/I2PSession.java +++ b/core/java/src/net/i2p/client/I2PSession.java @@ -143,6 +143,11 @@ public interface I2PSession { */ public Destination lookupDest(Hash h) throws I2PSessionException; + /** + * Get the current bandwidth limits + */ + public int[] bandwidthLimits() throws I2PSessionException; + /** See I2PSessionMuxedImpl for details */ public void addSessionListener(I2PSessionListener lsnr, int proto, int port); /** See I2PSessionMuxedImpl for details */ diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 0e13f2c563..5b7603fdd6 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -656,6 +656,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa return null; } + public int[] bandwidthLimits() throws I2PSessionException { + return null; + } + protected void updateActivity() { _lastActivity = _context.clock().now(); if (_isReduced) { diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index ae588e05f1..b417bd7f7a 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -130,7 +130,7 @@ class I2PSimpleSession extends I2PSessionImpl2 { return null; _bwReceivedLock = new Object(); sendMessage(new GetBandwidthLimitsMessage()); - for (int i = 0; i < 5 && !_destReceived; i++) { + for (int i = 0; i < 5 && !_bwReceived; i++) { try { synchronized (_bwReceivedLock) { _bwReceivedLock.wait(1000); diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index cf49ebcd48..3b4b1a6bed 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -280,15 +280,15 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi } /** - * Divide router limit by 2 for overhead. + * Divide router limit by 1.75 for overhead. * This could someday give a different answer to each client. * But it's not enforced anywhere. */ private void handleGetBWLimits(I2CPMessageReader reader, GetBandwidthLimitsMessage message) { if (_log.shouldLog(Log.INFO)) _log.info("Got BW Limits request"); - int in = _context.bandwidthLimiter().getInboundKBytesPerSecond() / 2; - int out = _context.bandwidthLimiter().getOutboundKBytesPerSecond() / 2; + int in = _context.bandwidthLimiter().getInboundKBytesPerSecond() * 4 / 7; + int out = _context.bandwidthLimiter().getOutboundKBytesPerSecond() * 4 / 7; BandwidthLimitsMessage msg = new BandwidthLimitsMessage(in, out); try { _runner.doSend(msg); From 29df534161b1f914e9abcf7f6012232282314835 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 26 Mar 2009 00:02:29 +0000 Subject: [PATCH 054/112] update license splash text --- installer/resources/readme.license.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/installer/resources/readme.license.txt b/installer/resources/readme.license.txt index d707e557c2..9e40f595cf 100644 --- a/installer/resources/readme.license.txt +++ b/installer/resources/readme.license.txt @@ -16,12 +16,13 @@ following non-public domain code: * Bouncycastle's hash routines (MIT license) * Cryptix's AES routines (Cryptix license) * Adam Buckley's SNTP routines (BSD) +* FSF's PRNG and GMP (LGPL) Also included in this distribution are a bunch of third party client applications, all with their own dependencies. Please see our license policy page for details: - http://www.i2p.net/licenses + http://www.i2p2.de/licenses One of the bundled client apps (routerconsole) requires us to say: @@ -29,8 +30,11 @@ requires us to say: the Apache Software Foundation (http://www.apache.org/) -Another (I2PTunnel) is GPL licensed. +I2PTunnel, I2PSnark, SusiDNS, and SusiMail +are GPL licensed. + +For more information see LICENSE.txt +in the install directory. For source, please see: - http://www.i2p.net/download -or http://www.i2p.net/cvs \ No newline at end of file + http://www.i2p2.de/monotone From 6a6cd14398d790d32ad8b56c53f2495c38b298f1 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 26 Mar 2009 18:28:27 +0000 Subject: [PATCH 055/112] checklist update --- checklist.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/checklist.txt b/checklist.txt index b649bef590..fcbe650284 100644 --- a/checklist.txt +++ b/checklist.txt @@ -15,45 +15,45 @@ Change revision in: core/java/src/net/i2p/CoreVersion.java Review the complete diff from the last release: - mtn diff -r t:i2p-0.6.(xx-1) > out.diff + mtn diff -r t:i2p-0.7.(xx-1) > out.diff vi out.diff Build and tag: ant pkg mtn ci - mtn tag h: i2p-0.6.xx + mtn tag h: i2p-0.7.xx Sync with mtn.i2p2.i2p Create a signed update file with: export I2P=~/i2p - java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign i2pupdate.zip i2pupdate.sud /path/to/private.key 0.6.xx + java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign i2pupdate.zip i2pupdate.sud /path/to/private.key 0.7.xx Verify signed update file with: java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion i2pupdate.sud java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate verifysig i2pupdate.sud Make the source tarball: - Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p i2p-0.6.xx + Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p i2p-0.7.xx Double-check trust list - tar cjf i2psource-0.6.xx.tar.bz2 --exclude i2p-0.6.xx/_MTN i2p-0.6.xx - mv i2p-0.6.xx.tar.bz2 i2p.i2p + tar cjf i2psource-0.7.xx.tar.bz2 --exclude i2p-0.7.xx/_MTN i2p-0.7.xx + mv i2p-0.7.xx.tar.bz2 i2p.i2p Until the build script gets this ability, you need to rename some files: - mv i2pinstall.exe i2pinstall-0.6.xx.exe - mv i2p.tar.bz2 i2pheadless-0.6.xx.tar.bz2 - mv i2pupdate.zip i2pupdate-0.6.xx.zip + mv i2pinstall.exe i2pinstall-0.7.xx.exe + mv i2p.tar.bz2 i2pheadless-0.7.xx.tar.bz2 + mv i2pupdate.zip i2pupdate-0.7.xx.zip you probably don't need to rename i2pupdate.sud Generate hashes: - sha1sum i2p*0.6.xx.* + sha1sum i2p*0.7.xx.* sha1sum i2pupdate.sud now GPG-sign an announcement with the hashes Generate PGP signatures: - gpg -b i2pinstall-0.6.xx.exe - gpg -b i2pheadless-0.6.xx.tar.bz2 - gpg -b i2psource-0.6.xx.tar.bz2 - gpg -b i2pupdate-0.6.xx.zip + gpg -b i2pinstall-0.7.xx.exe + gpg -b i2pheadless-0.7.xx.tar.bz2 + gpg -b i2psource-0.7.xx.tar.bz2 + gpg -b i2pupdate-0.7.xx.zip gpg -b i2pupdate.sud Distribute files to download locations and to www.i2p2.i2p From 0343e8ffcd7ba0465aabdd3e1ca20472ac3d7676 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 26 Mar 2009 18:51:43 +0000 Subject: [PATCH 056/112] readme_fr - thanks Narya and Mathiasdm --- build.xml | 3 ++- readme.html | 2 +- readme_de.html | 2 +- readme_fr.html | 37 +++++++++++++++++++++++++++++++++++++ readme_nl.html | 2 +- readme_sv.html | 4 ++-- 6 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 readme_fr.html diff --git a/build.xml b/build.xml index a9f58ef202..ef84b2aab3 100644 --- a/build.xml +++ b/build.xml @@ -276,6 +276,7 @@ + - + diff --git a/readme.html b/readme.html index 9ba805a721..0e4891b5d8 100644 --- a/readme.html +++ b/readme.html @@ -1,4 +1,4 @@ -

      English | Deutsch | Nederlands | Svenska

      +

      English | Deutsch | Français | Nederlands | Svenska

      If you've just started I2P, the Active: numbers on the left should start to grow over the next few minutes and you'll see a "shared clients" local destination listed on the left (if not, see below). Once those show up, diff --git a/readme_de.html b/readme_de.html index a6d233d6a1..1534585dab 100644 --- a/readme_de.html +++ b/readme_de.html @@ -1,4 +1,4 @@ -

      English | Deutsch | Nederlands | Svenska

      +

      English | Deutsch | Français | Nederlands | Svenska

      Wenn Du gerade I2P gestartet hast, sollten die "Active:" Zahlen links in den nächsten paar Minuten anwachsen und Du siehst dann dort ein "shared clients" lokales Ziel gelistet (falls nicht, siehe Unten). Sobald das erscheint, kannst Du:

      • "Eepsites" besuchen - In I2P sind anonym gehostete Websites - diff --git a/readme_fr.html b/readme_fr.html new file mode 100644 index 0000000000..5e619cff2b --- /dev/null +++ b/readme_fr.html @@ -0,0 +1,37 @@ +

        Deutsch | English | Français | Nederlands | Svenska

        +

        Si vous venez juste de lancer I2P, les chiffres sur la gauche à coté de Active devraient commencer à augmenter dans les prochaines minutes et vous verrez un "Shared client" en destination locale listés sur la gauche (si non, voir plus bas). Une fois qu'ils apparaissent, vous pouvez:

        +
          +
        • parcourir les "eepsites" - sur I2P il y a des sites web anonymes hébergés - dites à votre navigateur d'utiliser le HTTP proxy a l'adresse localhost port 4444, ensuite vous pouvez naviguer sur les eepsites. + + Il y a bien plus d'eepsites - suivez juste les liens au départ de ceux sur lesquels vous êtes, mettez-les dans vos favoris et visitez-les souvent!
        • +
        • Parcourez le web - Il y a pour l'instant un outproxy HTTP sur I2P attaché à votre propre proxy HTTP sur le port 4444 - vous devez simplement configurer le proxy de votre navigateur pour l'utiliser (comme expliqué ci-dessus) et aller sur n'importe quel URL normale - vos requêtes seront relayées par le réseau i2p.
        • +
        • Transfer de fichiers - Il y a un port intégré de Snark le client BitTorrent.
        • +
        • Utiliser le service de mail anonyme - Postman a créé un sytème de mails compatible avec un client de messagerie normal (POP3 / SMTP) qui permet d'envoyer des emails autant au sein d'i2p que vers et à partir de l'internet normal! Créez-vous un compte à hq.postman.i2p. + Nous fournissons dans la version de base de i2p susimail, + un client web pop3/smtp orienté sur l'anonymat qui est configuré pour accéder aux services email de postman.
        • +
        • Chatter de manière anonyme - Activez votre client IRC et connectez-le sur le serveur localhost port 6668. Ceci pointe vers l'un des deux serveur IRC anonyme, mais ni vous ni eux ne savent qui est l'autre
        • +
        • Créez-vous un blog anonyme - Renseignez-vous chez Syndie
        • +
        • Et bien d'autres
        • +
        + +

        Vous voulez votre propre eepsite?

        + +

        Nous fournissons de base quelques logiciels pour vous permettre de créer votre propre eepsite - une instance +Jetty, qui écoute sur +http://localhost:7658/. Placer simplement vos fichiers dans le répertoire eepsite/docroot/ (ou placez n'importe quel fichier JSP/Servlet standard .war) dans eepsite/webapps, ou script CGI standard dans eepsite/cgi-bin) et ils apparaitront. Après avoir démarré un tunnel pour votre eepsite (le tunnel doit pointer sur l'adresse locale du eepsite), votre eepsite sera visible pour les autes. Des instructions plus détaillées pour créer un eepsite se trouvent sur Votre eepsite temporaire. +

        + +

        Dépannage

        + +

        Soyez patient - i2p peut s'avérer lent à démarrer la première fois car il recherche des pairs. Si, après 30 minutes, votre Actives: connecté/récent compte moins de 10 pairs connectés, vous devez ouvrir le port 8887 sur votre pare-feu pour avoir une meilleure connection. Si vous ne pouvez accéder à aucun eepsite (même www.i2p2.i2p), soyez sûr que votre navigateur utilise bien le proxy localhost sur le port 4444. Vous pouvez aussi faire part de votre démarche sur le site web I2P, poster des message sur le forum de discussion, ou passer par #i2p ou #i2p-chat sur IRC sur le serveur irc.freenode.net, irc.postman.i2p ou irc.freshcoffee.i2p (ils sont liés).

        + +

        Comme vous pouvez le remarquer, il suffit d'éditer la page "docs/readme.html" pour changer la page d'acceuil

        diff --git a/readme_nl.html b/readme_nl.html index 3203c79b5d..427f75f912 100644 --- a/readme_nl.html +++ b/readme_nl.html @@ -1,4 +1,4 @@ -

        English | Deutsch | Nederlands | Svenska

        +

        English | Deutsch | Français | Nederlands | Svenska

        Als je net I2P opgestart hebt, zullen de 'Active:' (Actieve) getallen aan de linkerkant in de komende minuten stijgen, en je zal een "Shared clients" (Gedeelde clients) lokale bestemming zien staan aan de linkerkant (indien niet, zie hieronder). Eenmaal je deze bestemming ziet, kan je:

        • surfen naar "eepsites" - op I2P zijn er anonieme websites - stel je browser in om de HTTP proxy op localhost, poort 4444 te gebruiken, en surf vervolgens naar een eepsite - diff --git a/readme_sv.html b/readme_sv.html index e368fbc532..b1f59fa04d 100644 --- a/readme_sv.html +++ b/readme_sv.html @@ -1,5 +1,5 @@

          English -| Deutsch | Deutsch | Français | Nederlands | Svenska

          Om du just har startat I2P kommer de "Aktiva: #/#" börja öka inom några få minuter och du kommer se en destination kallad "delade @@ -98,4 +98,4 @@ href="irc://irc.freenode.net/#i2p">irc.freenode.net, irc.postman.i2p eller irc.freshcoffee.i2p (de är alla sammankopplade).

          Du kan förändra denhär sidan genom att ändra i filen -"docs/readme_sv.html"

          \ No newline at end of file +"docs/readme_sv.html"

          From fe0d0d6737a8a5f3ae8342a1cac73ec9122b34cf Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 27 Mar 2009 18:09:46 +0000 Subject: [PATCH 057/112] -11, catch rare AIOOB --- history.txt | 5 +++++ router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java | 5 +++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index d3fcf2f4ca..1495d99de2 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,8 @@ +2009-03-27 zzz + * Add readme_fr.html + * License splash update + * Catch rare TunnelGatewayMessage AIOOB, root cause unknown + 2009-03-24 zzz * I2PTunnel: - Add some warnings about new features diff --git a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java index f611c32138..8fc7c9fd9e 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java @@ -75,6 +75,11 @@ public class TunnelGatewayMessage extends I2NPMessageImpl { } DataHelper.toLong(out, curIndex, 2, _msgData.length); curIndex += 2; + // where is this coming from? + if (curIndex + _msgData.length > out.length) { + _log.log(Log.ERROR, "output buffer too small idx: " + curIndex + " len: " + _msgData.length + " outlen: " + out.length); + throw new I2NPMessageException("Too much data to write out (id=" + _tunnelId + " data=" + _msg + ")"); + } System.arraycopy(_msgData, 0, out, curIndex, _msgData.length); curIndex += _msgData.length; return curIndex; diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c1d6bdf7c6..510adb6dea 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 10; + public final static long BUILD = 11; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 4a9543be78e3ada39c9572680068133096b9f4e5 Mon Sep 17 00:00:00 2001 From: complication Date: Sun, 29 Mar 2009 19:47:46 +0000 Subject: [PATCH 058/112] * Update versions, package release --- core/java/src/net/i2p/CoreVersion.java | 2 +- history.txt | 5 +++ initialNews.xml | 4 +- installer/install.xml | 2 +- news.xml | 42 +++++++++---------- .../src/net/i2p/router/RouterVersion.java | 2 +- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/core/java/src/net/i2p/CoreVersion.java b/core/java/src/net/i2p/CoreVersion.java index c24542b009..6c924fe109 100644 --- a/core/java/src/net/i2p/CoreVersion.java +++ b/core/java/src/net/i2p/CoreVersion.java @@ -15,7 +15,7 @@ package net.i2p; */ public class CoreVersion { public final static String ID = "$Revision: 1.72 $ $Date: 2008-08-24 12:00:00 $"; - public final static String VERSION = "0.7"; + public final static String VERSION = "0.7.1"; public static void main(String args[]) { System.out.println("I2P Core version: " + VERSION); diff --git a/history.txt b/history.txt index 1495d99de2..e76d410559 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,8 @@ +* 2009-03-29 0.7.1 released + +2009-03-29 Complication + * Update versions, package release + 2009-03-27 zzz * Add readme_fr.html * License splash update diff --git a/initialNews.xml b/initialNews.xml index 6a9c4b4ca2..0306dcd7b2 100644 --- a/initialNews.xml +++ b/initialNews.xml @@ -1,5 +1,5 @@ - - +

          Congratulations on getting I2P installed!

            diff --git a/installer/install.xml b/installer/install.xml index 8c7ff1bf4a..125e92231c 100644 --- a/installer/install.xml +++ b/installer/install.xml @@ -4,7 +4,7 @@ i2p - 0.7 + 0.7.1 diff --git a/news.xml b/news.xml index 63adee7fa8..b85e0f1308 100644 --- a/news.xml +++ b/news.xml @@ -1,5 +1,5 @@ - - +

            • -2009-01-24: 0.7 Released +2009-03-29: 0.7.1 Released

            -The 0.7 release adds stability and flexibility to I2PSnark, -which can hopefully be used to distribute I2P updates in future. +The 0.7.1 release optimizes I2P towards better performance +and introduces new features.

            -The I2P router gets fixes and optimizations to various -transport-level and streaming issues, network exploration, -NetDB performance and the UDP introducer system. -Among other features, the new release offers -better connection limiting, higher tolerance to "out of memory" exceptions -in helper applications, and an experimental new address system -using Base32 hashes of destination keys (".b32.i2p" URLs). +Multiple bugs are fixed, replacements to the SimpleTimer class +should waste less time on object locking. Some old components +are dropped and several classes refactored to avoid repeating code.

            -Both the BOB and SAM protocols are improved upon, -more old components dropped, Router Console features added -and a possible latency measurement attack mitigated. -From this release onwards, block lists for misbehaving peers -are activated by default. +Support for encrypted LeaseSets (for creation of links over I2P +which an adversary cannot obstruct by attacking its gateways) +becomes more complete. New tunnel types like IRC server tunnels +and new options like delayed start and idling of tunnels +also gain support, along with improved usability of the I2P +Socks proxy mechanism.

            -It seems worthwhile to remind that already since -the last release, I2P requires Java 1.5 or higher. -If you are uncertain about your Java version, you can verify -by opening a terminal window or command prompt, -and entering the command "java -version". -If you have an older Java installed, please update it first!

            +Work continues on streamlining and expanding the Router Console, +on the BOB protocol, on I2P ports for Debian and Slackware Linux, +on the I2PSnark client, on TCP connection properties +and multiple other fronts. Updating is highly recommended. +

            diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 510adb6dea..450adcf289 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 11; + public final static long BUILD = 0; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 0c98d1843acf6d458bd69adfd59f34652dba39f7 Mon Sep 17 00:00:00 2001 From: sponge Date: Mon, 30 Mar 2009 05:31:40 +0000 Subject: [PATCH 059/112] Sponge fixes and additions patch: * 3 New jbigi build scripts and old ones fixed to work properly. * Some trivial BOB work. --- apps/BOB/bob.config | 2 +- apps/BOB/nbproject/build-impl.xml | 2 +- apps/BOB/nbproject/genfiles.properties | 4 +- apps/BOB/nbproject/project.properties | 33 +++--- apps/BOB/src/net/i2p/BOB/TCPio.java | 3 + core/c/jbigi/build-all.sh | 5 +- core/c/jbigi/build.sh | 2 +- core/c/jbigi/build_jbigi.sh | 6 +- core/c/jbigi/mbuild-all.sh | 140 +++++++++++++++++++++++++ core/c/jbigi/mbuild_jbigi.sh | 47 +++++++++ core/c/mbuild.sh | 28 +++++ 11 files changed, 245 insertions(+), 27 deletions(-) create mode 100755 core/c/jbigi/mbuild-all.sh create mode 100755 core/c/jbigi/mbuild_jbigi.sh create mode 100755 core/c/mbuild.sh diff --git a/apps/BOB/bob.config b/apps/BOB/bob.config index f9c28d3821..810d65b295 100644 --- a/apps/BOB/bob.config +++ b/apps/BOB/bob.config @@ -7,7 +7,7 @@ i2cp.tcp.port=7654 BOB.host=localhost inbound.lengthVariance=0 i2cp.messageReliability=BestEffort -BOB.port=45067 +BOB.port=45678 outbound.length=1 inbound.length=1 outbound.lengthVariance=0 diff --git a/apps/BOB/nbproject/build-impl.xml b/apps/BOB/nbproject/build-impl.xml index 9feea8e039..d8ed4de114 100644 --- a/apps/BOB/nbproject/build-impl.xml +++ b/apps/BOB/nbproject/build-impl.xml @@ -152,7 +152,7 @@ is divided into following sections: - + diff --git a/apps/BOB/nbproject/genfiles.properties b/apps/BOB/nbproject/genfiles.properties index dfc3734b8a..ca344b0d35 100644 --- a/apps/BOB/nbproject/genfiles.properties +++ b/apps/BOB/nbproject/genfiles.properties @@ -4,5 +4,5 @@ build.xml.stylesheet.CRC32=958a1d3e # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. nbproject/build-impl.xml.data.CRC32=209349b6 -nbproject/build-impl.xml.script.CRC32=75fac64c -nbproject/build-impl.xml.stylesheet.CRC32=e55b27f5 +nbproject/build-impl.xml.script.CRC32=c51e188e +nbproject/build-impl.xml.stylesheet.CRC32=65b8de21 diff --git a/apps/BOB/nbproject/project.properties b/apps/BOB/nbproject/project.properties index 76e318ff0a..7a94ff6ddd 100644 --- a/apps/BOB/nbproject/project.properties +++ b/apps/BOB/nbproject/project.properties @@ -24,18 +24,22 @@ dist.dir=dist dist.jar=${dist.dir}/BOB.jar dist.javadoc.dir=${dist.dir}/javadoc excludes= +file.reference.build-javadoc=../../../i2p.i2p/build/javadoc file.reference.core.jar=../i2p.i2p/core/dist/core.jar file.reference.i2p.jar=../../bob/i2p/i2p.i2p/build/i2p.jar -file.reference.i2p.jar-1=../../core/java/build/i2p.jar +file.reference.i2p.jar-1=../../build/i2p.jar file.reference.i2p.jar-2=../i2p.i2p/core/java/build/i2p.jar -file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar +file.reference.i2p.jar-3=../../../i2p.i2p/build/i2p.jar +file.reference.i2ptunnel.jar=../../../i2p.i2p/build/i2ptunnel.jar file.reference.java-src=../i2p.i2p/core/java/src/ file.reference.jbigi.jar=../../bob/i2p/i2p.i2p/build/jbigi.jar file.reference.mstreaming.jar=../../bob/i2p/i2p.i2p/build/mstreaming.jar -file.reference.mstreaming.jar-1=../ministreaming/java/build/mstreaming.jar +file.reference.mstreaming.jar-1=../../build/mstreaming.jar +file.reference.mstreaming.jar-2=../../../i2p.i2p/build/mstreaming.jar file.reference.NetBeansProjects-i2p.i2p=../i2p.i2p/ +file.reference.router.jar=../../build/router.jar file.reference.streaming.jar=../../bob/i2p/i2p.i2p/build/streaming.jar -file.reference.streaming.jar-1=../streaming/java/build/streaming.jar +file.reference.streaming.jar-1=../../../i2p.i2p/build/streaming.jar file.reference.wrapper-freebsd=../../installer/lib/wrapper/freebsd/ file.reference.wrapper-linux=../../installer/lib/wrapper/linux/ file.reference.wrapper-linux64=../../installer/lib/wrapper/linux64/ @@ -43,24 +47,17 @@ file.reference.wrapper-macosx=../../installer/lib/wrapper/macosx/ file.reference.wrapper-solaris=../../installer/lib/wrapper/solaris/ file.reference.wrapper-win32=../../installer/lib/wrapper/win32/ file.reference.wrapper.jar=../../installer/lib/wrapper/linux/wrapper.jar -file.reference.wrapper.jar-1=../../installer/lib/wrapper/freebsd/wrapper.jar -file.reference.wrapper.jar-2=../../installer/lib/wrapper/linux64/wrapper.jar -file.reference.wrapper.jar-3=../../installer/lib/wrapper/macosx/wrapper.jar -file.reference.wrapper.jar-4=../../installer/lib/wrapper/solaris/wrapper.jar -file.reference.wrapper.jar-5=../../installer/lib/wrapper/win32/wrapper.jar includes=** jar.compress=false javac.classpath=\ - ${file.reference.i2p.jar-1}:\ - ${file.reference.i2ptunnel.jar}:\ - ${file.reference.mstreaming.jar-1}:\ - ${file.reference.streaming.jar-1}:\ - ${file.reference.wrapper.jar-1}:\ ${file.reference.wrapper.jar}:\ - ${file.reference.wrapper.jar-2}:\ - ${file.reference.wrapper.jar-3}:\ - ${file.reference.wrapper.jar-4}:\ - ${file.reference.wrapper.jar-5} + ${file.reference.streaming.jar-1}:\ + ${file.reference.i2ptunnel.jar}:\ + ${file.reference.i2p.jar-1}:\ + ${file.reference.router.jar}:\ + ${file.reference.mstreaming.jar-1}:\ + ${file.reference.mstreaming.jar-2}:\ + ${file.reference.i2p.jar-3} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java index 41bb7cbe49..c9f4ab64cd 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPio.java +++ b/apps/BOB/src/net/i2p/BOB/TCPio.java @@ -76,6 +76,9 @@ public class TCPio implements Runnable { * * --Sponge * + * Tested with 128 bytes, and there was no performance gain. + * + * --Sponge */ int b; diff --git a/core/c/jbigi/build-all.sh b/core/c/jbigi/build-all.sh index 65456ee50b..861a1e0a4e 100755 --- a/core/c/jbigi/build-all.sh +++ b/core/c/jbigi/build-all.sh @@ -12,7 +12,7 @@ FreeBSD*) exit;; esac -VER=4.2.2 +VER=4.2.4 echo "Extracting GMP Version $VER ..." tar -xjf gmp-$VER.tar.bz2 echo "Building..." @@ -21,7 +21,8 @@ mkdir lib mkdir lib/net mkdir lib/net/i2p mkdir lib/net/i2p/util -for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon + +for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon geode pentiumm core2 do mkdir bin/$x cd bin/$x diff --git a/core/c/jbigi/build.sh b/core/c/jbigi/build.sh index 39969b13c9..7ef9000704 100755 --- a/core/c/jbigi/build.sh +++ b/core/c/jbigi/build.sh @@ -15,7 +15,7 @@ mkdir -p lib/ mkdir -p bin/local -VER=4.2.2 +VER=4.2.4 if [ "$1" != "dynamic" -a ! -d gmp-$VER ] then diff --git a/core/c/jbigi/build_jbigi.sh b/core/c/jbigi/build_jbigi.sh index 859eda329a..8ff3219b8a 100755 --- a/core/c/jbigi/build_jbigi.sh +++ b/core/c/jbigi/build_jbigi.sh @@ -1,6 +1,7 @@ #!/bin/sh # When executed in Mingw: Produces an jbigi.dll -# When executed in Linux: Produces an libjbigi.so +# When executed in Linux/FreeBSD: Produces an libjbigi.so +# Darwin produces libjbigi.jnilib, right? CC="gcc" @@ -32,7 +33,8 @@ esac #To link dynamically to GMP (use libgmp.so or gmp.lib), uncomment the first line below #To link statically to GMP, uncomment the second line below -if test $1 = "dynamic" +# Bug!!! Quote *BOTH* or neither! --Sponge +if test "$1" = "dynamic" then echo "Building jbigi lib that is dynamically linked to GMP" LIBPATH="-L.libs" diff --git a/core/c/jbigi/mbuild-all.sh b/core/c/jbigi/mbuild-all.sh new file mode 100755 index 0000000000..a97139dfca --- /dev/null +++ b/core/c/jbigi/mbuild-all.sh @@ -0,0 +1,140 @@ +#/bin/bash + +# TO-DO: Darwin. + +# Note: You will have to add the CPU ID for the platform in the CPU ID code +# for a new CPU. Just adding them here won't let I2P use the code! + +# +# If you know of other platforms i2p on linux works on, +# please add them here. +# Do NOT add any X86 platforms, do that below in the x86 platform list. +# +MISC_LINUX_PLATFORMS="hppa2.0 alphaev56 armv5tel mips64el itanium itanium2 ultrasparc2 ultrasparc2i alphaev6 powerpc970 powerpc7455 powerpc7447 atom" + +# +# If you know of other platforms i2p on FREEBSD works on, +# please add them here. +# Do NOT add any X86 platforms, do that below in the x86 platform list. +# +MISC_FREEBSD_PLATFORMS="atom alphaev56 ultrasparc2i" + +# +# MINGW/Windows?? +# +MISC_MINGW_PLATFORMS="" + +# +# Are there any other X86 platforms that work on i2p? Add them here. +# +# Oddly athlon64 builds.... I wonder what others can :-) +# +X86_PLATFORMS="pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon pentiumm core2 athlon64 geode" + + +# +# You should not need to edit anything below this comment. +# + +MINGW_PLATFORMS="${X86_PLATFORMS} ${MISC_MINGW_PLATFORMS}" +LINUX_PLATFORMS="${X86_PLATFORMS} ${MISC_LINUX_PLATFORMS}" +FREEBSD_PLATFORMS="${X86_PLATFORMS} ${MISC_FREEBSD_PLATFORMS}" + +VER=$(echo gmp-*.tar.bz2 | sed -re "s/(.*-)(.*)(.*.tar.bz2)$/\2/" | tail --lines=1) +if [ "$VER" == "" ] ; then + echo "ERROR! Can't find gmp source tarball." + exit 1 +fi + + +case `uname -sr` in +MINGW*) + PLATFORM_LIST="${MINGW_PLATFORMS}" + NAME="jbigi" + TYPE="dll" + TARGET="-windows-" + echo "Building windows .dlls for all architectures";; +Linux*) + PLATFORM_LIST="${LINUX_PLATFORMS}" + NAME="libjbigi" + TYPE="so" + TARGET="-linux-" + echo "Building linux .sos for all architectures";; +FreeBSD*) + PLATFORM_LIST="${FREEBSD_PLATFORMS}" + NAME="libjbigi" + TYPE="so" + TARGET="-freebsd-" + echo "Building freebsd .sos for all architectures";; +*) + echo "Unsupported build environment" + exit;; +esac + +function make_static { + echo "Attempting .${4} creation for ${3}${5}${2}" + ../../mbuild_jbigi.sh static || return 1 + cp ${3}.${4} ../../lib/net/i2p/util/${3}${5}${2}.${4} + return 0 +} + +function make_file { + # Nonfatal bail out on Failed build. + echo "Attempting build for ${3}${5}${2}" + make && return 0 + cd .. + rm -R "$2" + echo -e "\n\nFAILED! ${3}${5}${2} not made.\a" + sleep 10 + return 1 +} + +function configure_file { + echo -e "\n\n\nAttempting configure for ${3}${5}${2}\n\n\n" + sleep 10 + # Nonfatal bail out on unsupported platform. + ../../gmp-${1}/configure --build=${2} && return 0 + cd .. + rm -R "$2" + echo -e "\n\nSorry, ${3}${5}${2} is not supported on your build environment.\a" + sleep 10 + return 1 +} + +function build_file { + configure_file "$1" "$2" "$3" "$4" "$5" && make_file "$1" "$2" "$3" "$4" "$5" && make_static "$1" "$2" "$3" "$4" "$5" && return 0 + echo -e "\n\n\nError building static!\n\n\a" + sleep 10 + return 1 +} + +echo "Extracting GMP Version $VER ..." +tar -xf gmp-$VER.tar.bz2 || ( echo "Error in tarball file!" ; exit 1 ) + +if [ ! -d bin ]; then + mkdir bin +fi +if [ ! -d lib/net/i2p/util ]; then + mkdir -p lib/net/i2p/util +fi + +# Don't touch this one. +NO_PLATFORM=none + +for x in $NO_PLATFORM $PLATFORM_LIST +do + ( + if [ ! -d bin/$x ]; then + mkdir bin/$x + cd bin/$x + else + cd bin/$x + rm -Rf * + fi + + build_file "$VER" "$x" "$NAME" "$TYPE" "$TARGET" + ) +done + +echo "Success!" +exit 0 diff --git a/core/c/jbigi/mbuild_jbigi.sh b/core/c/jbigi/mbuild_jbigi.sh new file mode 100755 index 0000000000..1e262a6031 --- /dev/null +++ b/core/c/jbigi/mbuild_jbigi.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# When executed in Mingw: Produces an jbigi.dll +# When executed in Linux/FreeBSD: Produces an libjbigi.so +# What does Darwin produce? libjbigi.jnilib? +CC="gcc" + +case `uname -sr` in +MINGW*) + JAVA_HOME="c:/software/j2sdk1.4.2_05" + COMPILEFLAGS="-Wall" + INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include/win32/ -I$JAVA_HOME/include/" + LINKFLAGS="-shared -Wl,--kill-at" + LIBFILE="jbigi.dll";; +CYGWIN*) + JAVA_HOME="c:/software/j2sdk1.4.2_05" + COMPILEFLAGS="-Wall -mno-cygwin" + INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include/win32/ -I$JAVA_HOME/include/" + LINKFLAGS="-shared -Wl,--kill-at" + LIBFILE="jbigi.dll";; +Darwin*) + JAVA_HOME="/Library/Java/Home" + COMPILEFLAGS="-Wall" + INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include" + LINKFLAGS="-dynamiclib -framework JavaVM" + LIBFILE="libjbigi.jnilib";; +*) + COMPILEFLAGS="-fPIC -Wall" + INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include -I$JAVA_HOME/include/linux" + LINKFLAGS="-shared -Wl,-soname,libjbigi.so" + LIBFILE="libjbigi.so";; +esac + +if [ "$1" = "dynamic" ] ; then + echo "Building a jbigi lib that is dynamically linked to GMP" + LIBPATH="-L.libs" + INCLUDELIBS="-lgmp" +else + echo "Building a jbigi lib that is statically linked to GMP" + STATICLIBS=".libs/libgmp.a" +fi + +echo "Compiling C code..." +rm -f jbigi.o $LIBFILE +$CC -c $COMPILEFLAGS $INCLUDES ../../jbigi/src/jbigi.c || exit 1 +$CC $LINKFLAGS $INCLUDES $INCLUDELIBS -o $LIBFILE jbigi.o $STATICLIBS || exit 1 + +exit 0 diff --git a/core/c/mbuild.sh b/core/c/mbuild.sh new file mode 100755 index 0000000000..4591d5b7c8 --- /dev/null +++ b/core/c/mbuild.sh @@ -0,0 +1,28 @@ +#/bin/bash + +JBIGI=../../../installer/lib/jbigi/jbigi.jar + +if [ -f jbigi.jarx ] ; then +JBIGI=../jbigi.jar +fi + +(cd jcpuid ; sh build.sh ; cd ..) +(cd jbigi ; sh mbuild-all.sh ; cd ..) + +mkdir t + +( + cd t + jar xf ../../../installer/lib/jbigi/jbigi.jar +) + +cp jbigi/lib/net/i2p/util/* t/ +cp jcpuid/lib/freenet/support/CPUInformation/* t/ + +( + cd t + jar cf ../jbigi.jar . +) + +rm -R t +echo "jbigi.jar Refreshed." From e692e18d44339b111014493b496dc092550c2550 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 30 Mar 2009 16:05:48 +0000 Subject: [PATCH 060/112] Peer Selection: - Limit peers to a max % of all tunnels with router.maxTunnelPercentage=nn, default 33 - Add chart to tunnels.jsp to see results --- .../i2p/router/DummyTunnelManagerFacade.java | 2 + .../net/i2p/router/TunnelManagerFacade.java | 4 + .../tunnel/pool/TunnelPeerSelector.java | 1 + .../i2p/router/tunnel/pool/TunnelPool.java | 4 +- .../router/tunnel/pool/TunnelPoolManager.java | 133 ++++++++++++++++++ 5 files changed, 142 insertions(+), 2 deletions(-) diff --git a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java index f7f204857a..aec0390fd3 100644 --- a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java +++ b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java @@ -10,6 +10,7 @@ package net.i2p.router; import java.io.IOException; import java.io.Writer; +import java.util.Set; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -44,6 +45,7 @@ class DummyTunnelManagerFacade implements TunnelManagerFacade { public void setInboundSettings(Hash client, TunnelPoolSettings settings) {} public void setOutboundSettings(Hash client, TunnelPoolSettings settings) {} public int getInboundBuildQueueSize() { return 0; } + public Set selectPeersInTooManyTunnels() { return null; } public void renderStatusHTML(Writer out) throws IOException {} public void restart() {} diff --git a/router/java/src/net/i2p/router/TunnelManagerFacade.java b/router/java/src/net/i2p/router/TunnelManagerFacade.java index a6c1c96146..da0482e6ff 100644 --- a/router/java/src/net/i2p/router/TunnelManagerFacade.java +++ b/router/java/src/net/i2p/router/TunnelManagerFacade.java @@ -10,6 +10,7 @@ package net.i2p.router; import java.io.IOException; import java.io.Writer; +import java.util.Set; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -62,6 +63,9 @@ public interface TunnelManagerFacade extends Service { /** count how many inbound tunnel requests we have received but not yet processed */ public int getInboundBuildQueueSize(); + /** @return Set of peers that should not be allowed to be in another tunnel */ + public Set selectPeersInTooManyTunnels(); + /** * the client connected (or updated their settings), so make sure we have * the tunnels for them, and whenever necessary, ask them to authorize diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java index 1e0247c022..92e4545171 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -176,6 +176,7 @@ public abstract class TunnelPeerSelector { Set peers = new HashSet(1); peers.addAll(ctx.profileOrganizer().selectPeersRecentlyRejecting()); + peers.addAll(ctx.tunnelManager().selectPeersInTooManyTunnels()); // if (false && filterUnreachable(ctx, isInbound, isExploratory)) { if (filterUnreachable(ctx, isInbound, isExploratory)) { List caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java index 06a9b49991..699a8be9f1 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -28,7 +28,7 @@ public class TunnelPool { private RouterContext _context; private Log _log; private TunnelPoolSettings _settings; - private ArrayList _tunnels; + private ArrayList _tunnels; private TunnelPeerSelector _peerSelector; private TunnelPoolManager _manager; private boolean _alive; @@ -227,7 +227,7 @@ public class TunnelPool { * * @return list of TunnelInfo objects */ - public List listTunnels() { + public List listTunnels() { synchronized (_tunnels) { return new ArrayList(_tunnels); } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java index 58637ce63a..acbb9345db 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -6,9 +6,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.DataHelper; import net.i2p.data.Destination; @@ -506,6 +509,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { out.write("
    \n"); out.write("Inactive participating tunnels: " + inactive + "
    \n"); out.write("Lifetime bandwidth usage: " + DataHelper.formatSize(processed*1024) + "B
    \n"); + renderPeers(out); } class TunnelComparator implements Comparator { @@ -579,6 +583,135 @@ public class TunnelPoolManager implements TunnelManagerFacade { DataHelper.formatSize(processedOut*1024) + "B out
    "); } + private void renderPeers(Writer out) throws IOException { + // count up the peers in the local pools + HashCounter lc = new HashCounter(); + int tunnelCount = countTunnelsPerPeer(lc); + + // count up the peers in the participating tunnels + HashCounter pc = new HashCounter(); + int partCount = countParticipatingPerPeer(pc); + + Set peers = new HashSet(lc.hashes()); + peers.addAll(pc.hashes()); + List peerList = new ArrayList(peers); + Collections.sort(peerList, new HashComparator()); + + out.write("

    Tunnel Counts By Peer:

    \n"); + out.write("\n"); + for (Hash h : peerList) { + out.write("
    PeerExpl. + Client% of totalPart. from + to% of total
    "); + out.write(netDbLink(h)); + out.write("" + lc.count(h)); + out.write(""); + if (tunnelCount > 0) + out.write("" + (lc.count(h) * 100 / tunnelCount)); + else + out.write('0'); + out.write("" + pc.count(h)); + out.write(""); + if (partCount > 0) + out.write("" + (pc.count(h) * 100 / partCount)); + else + out.write('0'); + out.write('\n'); + } + out.write("
    Tunnels" + tunnelCount); + out.write(" " + partCount); + out.write(" 
    \n"); + } + + /** @return total number of non-fallback expl. + client tunnels */ + private int countTunnelsPerPeer(HashCounter lc) { + List pools = new ArrayList(); + listPools(pools); + int tunnelCount = 0; + for (TunnelPool tp : pools) { + for (TunnelInfo info : tp.listTunnels()) { + if (info.getLength() > 1) { + tunnelCount++; + for (int j = 0; j < info.getLength(); j++) { + Hash peer = info.getPeer(j); + if (!_context.routerHash().equals(peer)) + lc.increment(peer); + } + } + } + } + return tunnelCount; + } + + private static final int DEFAULT_MAX_PCT_TUNNELS = 33; + /** + * For reliability reasons, don't allow a peer in more than x% of + * client and exploratory tunnels. + * + * This also will prevent a single huge-capacity (or malicious) peer from + * taking all the tunnels in the network (although it would be nice to limit + * the % of total network tunnels to 10% or so, but that appears to be + * too low to set as a default here... much lower than 33% will push client + * tunnels out of the fast tier into high cap or beyond...) + * + * Possible improvement - restrict based on count per IP, or IP block, + * to slightly increase costs of collusion + * + * @return Set of peers that should not be allowed in another tunnel + */ + public Set selectPeersInTooManyTunnels() { + HashCounter lc = new HashCounter(); + int tunnelCount = countTunnelsPerPeer(lc); + Set rv = new HashSet(); + if (tunnelCount >= 4 && _context.router().getUptime() > 10*60*1000) { + int max = _context.getProperty("router.maxTunnelPercentage", DEFAULT_MAX_PCT_TUNNELS); + for (Hash h : lc.hashes()) { + if (lc.count(h) > 0 && (lc.count(h) + 1) * 100 / (tunnelCount + 1) > max) + rv.add(h); + } + } + return rv; + } + + /** @return total number of part. tunnels */ + private int countParticipatingPerPeer(HashCounter pc) { + List participating = _context.tunnelDispatcher().listParticipatingTunnels(); + for (HopConfig cfg : participating) { + Hash from = cfg.getReceiveFrom(); + if (from != null) + pc.increment(from); + Hash to = cfg.getSendTo(); + if (to != null) + pc.increment(to); + } + return participating.size(); + } + + class HashComparator implements Comparator { + public int compare(Object l, Object r) { + return ((Hash)l).toBase64().compareTo(((Hash)r).toBase64()); + } + } + + private static class HashCounter { + private ConcurrentHashMap _map; + public HashCounter() { + _map = new ConcurrentHashMap(); + } + public void increment(Hash h) { + Integer i = _map.putIfAbsent(h, Integer.valueOf(1)); + if (i != null) + _map.put(h, Integer.valueOf(i.intValue() + 1)); + } + public int count(Hash h) { + Integer i = _map.get(h); + if (i != null) + return i.intValue(); + return 0; + } + public Set hashes() { + return _map.keySet(); + } + } + private String getCapacity(Hash peer) { RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null) { From 58fc3a501dfa9e7e0b82e7cd34da2f6d1e33a15c Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 30 Mar 2009 16:13:58 +0000 Subject: [PATCH 061/112] -1 --- history.txt | 10 ++++++++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index e76d410559..f027db23a5 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,13 @@ +2009-03-30 zzz + * I2CP: + - Implement BandwidthLimitsMessage + - Have i2psnark use new message, remove + build dependency on router + * Peer Selection: + - Limit peers to a max % of all tunnels with + router.maxTunnelPercentage=nn, default 33 + - Add chart to tunnels.jsp to see results + * 2009-03-29 0.7.1 released 2009-03-29 Complication diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 450adcf289..1d4797d6fc 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 0; + public final static long BUILD = 1; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From f81a24a0cc1856ee18f76db8ba6d2cf68c6c8639 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 1 Apr 2009 04:54:49 +0000 Subject: [PATCH 062/112] I2PTunnel: Fix tunnel close http://forum.i2p/viewtopic.php?t=3231 broken in 0.7-8 --- .../java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java | 1 + history.txt | 4 ++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index b6eb392243..f5f0df6b98 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -278,6 +278,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } sockManager.setName("Client"); + tunnel.addSession(sockManager.getSession()); return sockManager; } diff --git a/history.txt b/history.txt index f027db23a5..9ac00e87b6 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,7 @@ +2009-04-01 zzz + * I2PTunnel: Fix tunnel close + http://forum.i2p/viewtopic.php?t=3231 + 2009-03-30 zzz * I2CP: - Implement BandwidthLimitsMessage diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 1d4797d6fc..a727710d68 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 1; + public final static long BUILD = 2; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 0b89171abd51681c18a8bf62cc8d47e8ba042a4a Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 1 Apr 2009 20:17:25 +0000 Subject: [PATCH 063/112] StatisticsManager - effective in 0.7.2: - Spoof uptime to 90m for all - Change tunnel stats from 10m to 60m --- .../src/net/i2p/router/StatisticsManager.java | 76 +++++-------------- .../i2p/router/tunnel/pool/BuildExecutor.java | 12 +-- 2 files changed, 27 insertions(+), 61 deletions(-) diff --git a/router/java/src/net/i2p/router/StatisticsManager.java b/router/java/src/net/i2p/router/StatisticsManager.java index d959c25d3b..4a286fe3d0 100644 --- a/router/java/src/net/i2p/router/StatisticsManager.java +++ b/router/java/src/net/i2p/router/StatisticsManager.java @@ -29,12 +29,9 @@ public class StatisticsManager implements Service { private Log _log; private RouterContext _context; private boolean _includePeerRankings; - private int _publishedStats; public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings"; public final static String DEFAULT_PROP_PUBLISH_RANKINGS = "true"; - public final static String PROP_MAX_PUBLISHED_PEERS = "router.publishPeerMax"; - public final static int DEFAULT_MAX_PUBLISHED_PEERS = 10; private final DecimalFormat _fmt; private final DecimalFormat _pct; @@ -52,45 +49,12 @@ public class StatisticsManager implements Service { startup(); } public void startup() { - String val = _context.router().getConfigSetting(PROP_PUBLISH_RANKINGS); - try { - if (val == null) { - if (_log.shouldLog(Log.INFO)) - _log.info("Peer publishing setting " + PROP_PUBLISH_RANKINGS - + " not set - using default " + DEFAULT_PROP_PUBLISH_RANKINGS); - val = DEFAULT_PROP_PUBLISH_RANKINGS; - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Peer publishing setting " + PROP_PUBLISH_RANKINGS - + " set to " + val); - } - boolean v = Boolean.TRUE.toString().equalsIgnoreCase(val); - _includePeerRankings = v; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Setting includePeerRankings = " + v); - } catch (Throwable t) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error determining whether to publish rankings [" - + PROP_PUBLISH_RANKINGS + "=" + val - + "], so we're defaulting to FALSE"); - _includePeerRankings = false; - } - val = _context.router().getConfigSetting(PROP_MAX_PUBLISHED_PEERS); - if (val == null) { - _publishedStats = DEFAULT_MAX_PUBLISHED_PEERS; - } else { - try { - int num = Integer.parseInt(val); - _publishedStats = num; - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Invalid max number of peers to publish [" + val - + "], defaulting to " + DEFAULT_MAX_PUBLISHED_PEERS, nfe); - _publishedStats = DEFAULT_MAX_PUBLISHED_PEERS; - } - } + String val = _context.getProperty(PROP_PUBLISH_RANKINGS, DEFAULT_PROP_PUBLISH_RANKINGS); + _includePeerRankings = Boolean.valueOf(val); } + static final boolean CommentOutIn072 = RouterVersion.VERSION.equals("0.7.1"); + /** Retrieve a snapshot of the statistics that should be published */ public Properties publishStatistics() { Properties stats = new Properties(); @@ -124,9 +88,6 @@ public class StatisticsManager implements Service { ***/ if (_includePeerRankings) { - if (false) - stats.putAll(_context.profileManager().summarizePeers(_publishedStats)); - long publishedUptime = _context.router().getUptime(); // Don't publish these for first hour if (publishedUptime > 60*60*1000) @@ -172,12 +133,16 @@ public class StatisticsManager implements Service { //includeRate("stream.con.sendDuplicateSize", stats, new long[] { 60*60*1000 }); //includeRate("stream.con.receiveDuplicateSize", stats, new long[] { 60*60*1000 }); - // Round smaller uptimes to 1 hour, to frustrate uptime tracking - // Round 2nd hour to 90m since peers use 2h minimum to route - if (publishedUptime < 60*60*1000) publishedUptime = 60*60*1000; - else if (publishedUptime < 2*60*60*1000) publishedUptime = 90*60*1000; - - stats.setProperty("stat_uptime", DataHelper.formatDuration(publishedUptime)); + if (CommentOutIn072) { + // Round smaller uptimes to 1 hour, to frustrate uptime tracking + // Round 2nd hour to 90m since peers use 2h minimum to route + if (publishedUptime < 60*60*1000) publishedUptime = 60*60*1000; + else if (publishedUptime < 2*60*60*1000) publishedUptime = 90*60*1000; + stats.setProperty("stat_uptime", DataHelper.formatDuration(publishedUptime)); + } else { + // So that we will still get build requests + stats.setProperty("stat_uptime", "90m"); + } //stats.setProperty("stat__rateKey", "avg;maxAvg;pctLifetime;[sat;satLim;maxSat;maxSatLim;][num;lifetimeFreq;maxFreq]"); //includeRate("tunnel.decryptRequestTime", stats, new long[] { 60*1000, 10*60*1000 }); @@ -185,12 +150,13 @@ public class StatisticsManager implements Service { //includeRate("udp.packetVerifyTime", stats, new long[] { 60*1000 }); //includeRate("tunnel.buildRequestTime", stats, new long[] { 10*60*1000 }); - includeRate("tunnel.buildClientExpire", stats, new long[] { 10*60*1000 }); - includeRate("tunnel.buildClientReject", stats, new long[] { 10*60*1000 }); - includeRate("tunnel.buildClientSuccess", stats, new long[] { 10*60*1000 }); - includeRate("tunnel.buildExploratoryExpire", stats, new long[] { 10*60*1000 }); - includeRate("tunnel.buildExploratoryReject", stats, new long[] { 10*60*1000 }); - includeRate("tunnel.buildExploratorySuccess", stats, new long[] { 10*60*1000 }); + long rate = CommentOutIn072 ? 10*60*1000 : 60*60*1000; + includeRate("tunnel.buildClientExpire", stats, new long[] { rate }); + includeRate("tunnel.buildClientReject", stats, new long[] { rate }); + includeRate("tunnel.buildClientSuccess", stats, new long[] { rate }); + includeRate("tunnel.buildExploratoryExpire", stats, new long[] { rate }); + includeRate("tunnel.buildExploratoryReject", stats, new long[] { rate }); + includeRate("tunnel.buildExploratorySuccess", stats, new long[] { rate }); //includeRate("tunnel.rejectTimeout", stats, new long[] { 10*60*1000 }); //includeRate("tunnel.rejectOverloaded", stats, new long[] { 10*60*1000 }); //includeRate("tunnel.acceptLoad", stats, new long[] { 10*60*1000 }); diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java index 3a84f48105..06a6663543 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java @@ -37,12 +37,12 @@ class BuildExecutor implements Runnable { _currentlyBuilding = new ArrayList(10); _context.statManager().createRateStat("tunnel.concurrentBuilds", "How many builds are going at once", "Tunnels", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); _context.statManager().createRateStat("tunnel.concurrentBuildsLagged", "How many builds are going at once when we reject further builds, due to job lag (period is lag)", "Tunnels", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); - _context.statManager().createRateStat("tunnel.buildExploratoryExpire", "How often an exploratory tunnel times out during creation", "Tunnels", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("tunnel.buildClientExpire", "How often a client tunnel times out during creation", "Tunnels", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("tunnel.buildExploratorySuccess", "Response time for success", "Tunnels", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("tunnel.buildClientSuccess", "Response time for success", "Tunnels", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("tunnel.buildExploratoryReject", "Response time for rejection", "Tunnels", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("tunnel.buildClientReject", "Response time for rejection", "Tunnels", new long[] { 60*1000, 10*60*1000 }); + _context.statManager().createRateStat("tunnel.buildExploratoryExpire", "How often an exploratory tunnel times out during creation", "Tunnels", new long[] { 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("tunnel.buildClientExpire", "How often a client tunnel times out during creation", "Tunnels", new long[] { 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("tunnel.buildExploratorySuccess", "Response time for success", "Tunnels", new long[] { 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("tunnel.buildClientSuccess", "Response time for success", "Tunnels", new long[] { 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("tunnel.buildExploratoryReject", "Response time for rejection", "Tunnels", new long[] { 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("tunnel.buildClientReject", "Response time for rejection", "Tunnels", new long[] { 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("tunnel.buildRequestTime", "How long it takes to build a tunnel request", "Tunnels", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("tunnel.buildRequestZeroHopTime", "How long it takes to build a zero hop tunnel", "Tunnels", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("tunnel.pendingRemaining", "How many inbound requests are pending after a pass (period is how long the pass takes)?", "Tunnels", new long[] { 60*1000, 10*60*1000 }); From 53cb80636a957db640c3c4444e2e8f08e060b5c1 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 18:12:38 +0000 Subject: [PATCH 064/112] remove dup comment in persisted profiles --- core/java/src/net/i2p/stat/RateStat.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/java/src/net/i2p/stat/RateStat.java b/core/java/src/net/i2p/stat/RateStat.java index 329706040c..dd1fe63e3f 100644 --- a/core/java/src/net/i2p/stat/RateStat.java +++ b/core/java/src/net/i2p/stat/RateStat.java @@ -134,7 +134,6 @@ public class RateStat { buf.append("# Period : ").append(DataHelper.formatDuration(_rates[i].getPeriod())).append(" for rate ") .append(_groupName).append(" - ").append(_statName).append(NL); buf.append(NL); - out.write(buf.toString().getBytes()); String curPrefix = prefix + "." + DataHelper.formatDuration(_rates[i].getPeriod()); _rates[i].store(curPrefix, buf); out.write(buf.toString().getBytes()); From f6bc9e87072a26ced4e22311751ae028d4b14efe Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 18:55:40 +0000 Subject: [PATCH 065/112] Profiles: - Remove unused calculators and RateStats: CapacityCalculator, StrictSpeedCalculator, IsFailingCalculator; sendFailureSize, processSuccessRate, processfailureRate, commErrorRate, tunnelTestResponseTimeSlow - Reduced number of Rates in these RateStats: sendSuccessSize, receiveSize, rejectRate, failRate - ~5KB/profile savings total - Deflate speed calculation once an hour instead of once a day, to improve fast tier selection --- .../src/net/i2p/router/RouterContext.java | 10 -- .../peermanager/IsFailingCalculator.java | 82 -------------- .../i2p/router/peermanager/PeerProfile.java | 103 +++--------------- .../peermanager/ProfileManagerImpl.java | 6 - .../router/peermanager/ProfileOrganizer.java | 4 +- .../peermanager/ProfilePersistenceHelper.java | 7 -- .../peermanager/ReliabilityCalculator.java | 91 ---------------- .../peermanager/StrictSpeedCalculator.java | 90 --------------- .../i2p/router/peermanager/TunnelHistory.java | 27 +---- 9 files changed, 21 insertions(+), 399 deletions(-) delete mode 100644 router/java/src/net/i2p/router/peermanager/IsFailingCalculator.java delete mode 100644 router/java/src/net/i2p/router/peermanager/ReliabilityCalculator.java delete mode 100644 router/java/src/net/i2p/router/peermanager/StrictSpeedCalculator.java diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 517a5ba358..e3b5e38466 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -16,9 +16,7 @@ import net.i2p.router.peermanager.IsFailingCalculator; import net.i2p.router.peermanager.PeerManagerFacadeImpl; import net.i2p.router.peermanager.ProfileManagerImpl; import net.i2p.router.peermanager.ProfileOrganizer; -import net.i2p.router.peermanager.ReliabilityCalculator; import net.i2p.router.peermanager.SpeedCalculator; -import net.i2p.router.peermanager.StrictSpeedCalculator; import net.i2p.router.transport.CommSystemFacadeImpl; import net.i2p.router.transport.FIFOBandwidthLimiter; import net.i2p.router.transport.OutboundMessageRegistry; @@ -65,9 +63,7 @@ public class RouterContext extends I2PAppContext { private Calculator _isFailingCalc; private Calculator _integrationCalc; private Calculator _speedCalc; - private Calculator _reliabilityCalc; private Calculator _capacityCalc; - private Calculator _oldSpeedCalc; private static List _contexts = new ArrayList(1); @@ -135,8 +131,6 @@ public class RouterContext extends I2PAppContext { _isFailingCalc = new IsFailingCalculator(this); _integrationCalc = new IntegrationCalculator(this); _speedCalc = new SpeedCalculator(this); - _oldSpeedCalc = new StrictSpeedCalculator(this); - _reliabilityCalc = new ReliabilityCalculator(this); _capacityCalc = new CapacityCalculator(this); } @@ -270,9 +264,6 @@ public class RouterContext extends I2PAppContext { public Calculator integrationCalculator() { return _integrationCalc; } /** how do we rank the speed of profiles? */ public Calculator speedCalculator() { return _speedCalc; } - public Calculator oldSpeedCalculator() { return _oldSpeedCalc; } - /** how do we rank the reliability of profiles? */ - public Calculator reliabilityCalculator() { return _reliabilityCalc; } /** how do we rank the capacity of profiles? */ public Calculator capacityCalculator() { return _capacityCalc; } @@ -301,7 +292,6 @@ public class RouterContext extends I2PAppContext { buf.append(_isFailingCalc).append('\n'); buf.append(_integrationCalc).append('\n'); buf.append(_speedCalc).append('\n'); - buf.append(_reliabilityCalc).append('\n'); return buf.toString(); } diff --git a/router/java/src/net/i2p/router/peermanager/IsFailingCalculator.java b/router/java/src/net/i2p/router/peermanager/IsFailingCalculator.java deleted file mode 100644 index 1391bd8a1e..0000000000 --- a/router/java/src/net/i2p/router/peermanager/IsFailingCalculator.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.i2p.router.peermanager; - -import net.i2p.router.RouterContext; -import net.i2p.util.Log; - -/** - * Simple boolean calculation to determine whether the given profile is "failing" - - * meaning we shouldn't bother trying to get them to do something. However, if we - * have a specific need to contact them in particular - e.g. instructions in a garlic - * or leaseSet - we will try. The currently implemented algorithm determines that - * a profile is failing if withing the last few minutes, they've done something bad:
      - *
    • It has a comm error (TCP disconnect, etc) in the last minute or two
    • - *
    • They've failed to respond to a db message in the last minute or two
    • - *
    • They've rejected a tunnel in the last 5 minutes
    • - *
    • They've been unreachable any time in the last 5 minutes
    • - *
    - * - */ -public class IsFailingCalculator extends Calculator { - private Log _log; - private RouterContext _context; - - /** if they haven't b0rked in the last 2 minutes, they're ok */ - private final static long GRACE_PERIOD = 2*60*1000; - - public IsFailingCalculator(RouterContext context) { - _context = context; - _log = context.logManager().getLog(IsFailingCalculator.class); - } - - public boolean calcBoolean(PeerProfile profile) { - // have we failed in the last 119 seconds? - /* - if ( (profile.getCommError().getRate(60*1000).getCurrentEventCount() > 0) || - (profile.getCommError().getRate(60*1000).getLastEventCount() > 0) || - (profile.getCommError().getRate(10*60*1000).getCurrentEventCount() > 0) ) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Peer " + profile.getPeer().toBase64() - + " is failing because it had a comm error recently "); - return true; - } else { - */ - //if ( (profile.getDBHistory().getFailedLookupRate().getRate(60*1000).getCurrentEventCount() > 0) || - // (profile.getDBHistory().getFailedLookupRate().getRate(60*1000).getLastEventCount() > 0) ) { - // // are they overloaded (or disconnected)? - // return true; - //} - - // this doesn't make sense with probabalistic rejections - we should be - // adequately dampening the capacity so these peers aren't queried - - //Rate rejectRate = profile.getTunnelHistory().getRejectionRate().getRate(10*60*1000); - //if (rejectRate.getCurrentEventCount() >= 2) { - // if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Peer " + profile.getPeer().toBase64() - // + " is failing because they rejected some tunnels recently"); - // return true; - //} - - //// - // the right way to behave would be to use some statistical - // analysis on the failure rate, and only mark the peer as failing - // if their rate exceeded the expected rate (mean, median, stddev, etc) - //// - - //Rate failedRate = profile.getTunnelHistory().getFailedRate().getRate(60*1000); - //if (failedRate.getCurrentEventCount() >= 2) { - // if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Peer " + profile.getPeer().toBase64() - // + " is failing because too many of their tunnels failed recently"); - // return true; - //} - - // if they have rejected us saying they're totally broken anytime in the last - // 10 minutes, dont bother 'em - if (profile.getTunnelHistory().getLastRejectedCritical() > _context.clock().now() - 10*60*1000) - return true; - - return false; - //} - } -} diff --git a/router/java/src/net/i2p/router/peermanager/PeerProfile.java b/router/java/src/net/i2p/router/peermanager/PeerProfile.java index 4a94100bda..5c058b4938 100644 --- a/router/java/src/net/i2p/router/peermanager/PeerProfile.java +++ b/router/java/src/net/i2p/router/peermanager/PeerProfile.java @@ -37,25 +37,19 @@ public class PeerProfile { private double _tunnelTestResponseTimeAvg; // periodic rates private RateStat _sendSuccessSize = null; - private RateStat _sendFailureSize = null; private RateStat _receiveSize = null; private RateStat _dbResponseTime = null; private RateStat _tunnelCreateResponseTime = null; private RateStat _tunnelTestResponseTime = null; - private RateStat _tunnelTestResponseTimeSlow = null; - private RateStat _commError = null; private RateStat _dbIntroduction = null; // calculation bonuses private long _speedBonus; - private long _reliabilityBonus; private long _capacityBonus; private long _integrationBonus; // calculation values private double _speedValue; - private double _reliabilityValue; private double _capacityValue; private double _integrationValue; - private double _oldSpeedValue; private boolean _isFailing; // good vs bad behavior private TunnelHistory _tunnelHistory; @@ -72,7 +66,6 @@ public class PeerProfile { _log = context.logManager().getLog(PeerProfile.class); _expanded = false; _speedValue = 0; - _reliabilityValue = 0; _capacityValue = 0; _integrationValue = 0; _isFailing = false; @@ -111,6 +104,11 @@ public class PeerProfile { * given period?) * Also mark active if it is connected, as this will tend to encourage use * of already-connected peers. + * + * Note: this appears to be the only use for these two RateStats. + * + * @param period must be one of the periods in the RateStat constructors below + * (5*60*1000 or 60*60*1000) */ public boolean getIsActive(long period) { if ( (getSendSuccessSize().getRate(period).getCurrentEventCount() > 0) || @@ -154,8 +152,6 @@ public class PeerProfile { /** how large successfully sent messages are, calculated over a 1 minute, 1 hour, and 1 day period */ public RateStat getSendSuccessSize() { return _sendSuccessSize; } - /** how large messages that could not be sent were, calculated over a 1 minute, 1 hour, and 1 day period */ - public RateStat getSendFailureSize() { return _sendFailureSize; } /** how large received messages are, calculated over a 1 minute, 1 hour, and 1 day period */ public RateStat getReceiveSize() { return _receiveSize; } /** how long it takes to get a db response from the peer (in milliseconds), calculated over a 1 minute, 1 hour, and 1 day period */ @@ -164,10 +160,6 @@ public class PeerProfile { public RateStat getTunnelCreateResponseTime() { return _tunnelCreateResponseTime; } /** how long it takes to successfully test a tunnel this peer participates in (in milliseconds), calculated over a 10 minute, 1 hour, and 1 day period */ public RateStat getTunnelTestResponseTime() { return _tunnelTestResponseTime; } - /** how long it takes to successfully test the peer (in milliseconds) when the time exceeds 5s */ - public RateStat getTunnelTestResponseTimeSlow() { return _tunnelTestResponseTimeSlow; } - /** how long between communication errors with the peer (disconnection, etc), calculated over a 1 minute, 1 hour, and 1 day period */ - public RateStat getCommError() { return _commError; } /** how many new peers we get from dbSearchReplyMessages or dbStore messages, calculated over a 1 hour, 1 day, and 1 week period */ public RateStat getDbIntroduction() { return _dbIntroduction; } @@ -179,14 +171,6 @@ public class PeerProfile { public long getSpeedBonus() { return _speedBonus; } public void setSpeedBonus(long bonus) { _speedBonus = bonus; } - /** - * extra factor added to the reliability ranking - this can be updated in the profile - * written to disk to affect how the algorithm ranks reliability. Negative values are - * penalties - */ - public long getReliabilityBonus() { return _reliabilityBonus; } - public void setReliabilityBonus(long bonus) { _reliabilityBonus = bonus; } - /** * extra factor added to the capacity ranking - this can be updated in the profile * written to disk to affect how the algorithm ranks capacity. Negative values are @@ -210,14 +194,6 @@ public class PeerProfile { * */ public double getSpeedValue() { return _speedValue; } - public double getOldSpeedValue() { return _oldSpeedValue; } - /** - * How likely are they to stay up and pass on messages over the next few minutes. - * Positive numbers means more likely, negative numbers means its probably not - * even worth trying. - * - */ - public double getReliabilityValue() { return _reliabilityValue; } /** * How many tunnels do we think this peer can handle over the next hour? * @@ -354,13 +330,10 @@ public class PeerProfile { */ public void shrinkProfile() { _sendSuccessSize = null; - _sendFailureSize = null; _receiveSize = null; _dbResponseTime = null; _tunnelCreateResponseTime = null; _tunnelTestResponseTime = null; - _tunnelTestResponseTimeSlow = null; - _commError = null; _dbIntroduction = null; _tunnelHistory = null; _dbHistory = null; @@ -378,21 +351,15 @@ public class PeerProfile { public void expandProfile() { String group = (null == _peer ? "profileUnknown" : _peer.toBase64().substring(0,6)); if (_sendSuccessSize == null) - _sendSuccessSize = new RateStat("sendSuccessSize", "How large successfully sent messages are", group, new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - if (_sendFailureSize == null) - _sendFailureSize = new RateStat("sendFailureSize", "How large messages that could not be sent were", group, new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } ); + _sendSuccessSize = new RateStat("sendSuccessSize", "How large successfully sent messages are", group, new long[] { 5*60*1000l, 60*60*1000l }); if (_receiveSize == null) - _receiveSize = new RateStat("receiveSize", "How large received messages are", group, new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000 } ); + _receiveSize = new RateStat("receiveSize", "How large received messages are", group, new long[] { 5*60*1000l, 60*60*1000l } ); if (_dbResponseTime == null) _dbResponseTime = new RateStat("dbResponseTime", "how long it takes to get a db response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } ); if (_tunnelCreateResponseTime == null) _tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000 } ); if (_tunnelTestResponseTime == null) _tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 } ); - if (_tunnelTestResponseTimeSlow == null) - _tunnelTestResponseTimeSlow = new RateStat("tunnelTestResponseTimeSlow", "how long it takes to successfully test a peer when the time exceeds 5s", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l, }); - if (_commError == null) - _commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", group, new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000 } ); if (_dbIntroduction == null) _dbIntroduction = new RateStat("dbIntroduction", "how many new peers we get from dbSearchReplyMessages or dbStore messages", group, new long[] { 60*60*1000l, 6*60*60*1000l, 24*60*60*1000l }); @@ -402,18 +369,17 @@ public class PeerProfile { _dbHistory = new DBHistory(_context, group); _sendSuccessSize.setStatLog(_context.statManager().getStatLog()); - _sendFailureSize.setStatLog(_context.statManager().getStatLog()); _receiveSize.setStatLog(_context.statManager().getStatLog()); _dbResponseTime.setStatLog(_context.statManager().getStatLog()); _tunnelCreateResponseTime.setStatLog(_context.statManager().getStatLog()); _tunnelTestResponseTime.setStatLog(_context.statManager().getStatLog()); - _tunnelTestResponseTimeSlow.setStatLog(_context.statManager().getStatLog()); - _commError.setStatLog(_context.statManager().getStatLog()); _dbIntroduction.setStatLog(_context.statManager().getStatLog()); _expanded = true; } /** once a day, on average, cut the measured throughtput values in half */ - private static final long DROP_PERIOD_MINUTES = 24*60; + /** let's try once an hour times 3/4 */ + private static final int DROP_PERIOD_MINUTES = 60; + private static final double DEGRADE_FACTOR = 0.75; private long _lastCoalesceDate = System.currentTimeMillis(); private void coalesceThroughput() { long now = System.currentTimeMillis(); @@ -430,46 +396,19 @@ public class PeerProfile { break; } } - - if (false && _log.shouldLog(Log.WARN) ) { - StringBuffer buf = new StringBuffer(128); - buf.append("Updating throughput after ").append(tot).append(" to "); - for (int i = 0; i < THROUGHPUT_COUNT; i++) - buf.append(_peakThroughput[i]).append(','); - buf.append(" for ").append(_peer.toBase64()); - _log.warn(buf.toString()); - } } else { - if (_context.random().nextLong(DROP_PERIOD_MINUTES*2) <= 0) { - for (int i = 0; i < THROUGHPUT_COUNT; i++) - _peakThroughput[i] /= 2; - - if (false && _log.shouldLog(Log.WARN) ) { - StringBuffer buf = new StringBuffer(128); - buf.append("Degrading the throughput measurements to "); - for (int i = 0; i < THROUGHPUT_COUNT; i++) - buf.append(_peakThroughput[i]).append(','); - buf.append(" for ").append(_peer.toBase64()); - _log.warn(buf.toString()); - } + if (_context.random().nextInt(DROP_PERIOD_MINUTES*2) <= 0) { + for (int i = 0; i < THROUGHPUT_COUNT; i++) + _peakThroughput[i] *= DEGRADE_FACTOR; } } // we degrade the tunnel throughput here too, regardless of the current // activity - if (_context.random().nextLong(DROP_PERIOD_MINUTES*2) <= 0) { + if (_context.random().nextInt(DROP_PERIOD_MINUTES*2) <= 0) { for (int i = 0; i < THROUGHPUT_COUNT; i++) { - _peakTunnelThroughput[i] /= 2; - _peakTunnel1mThroughput[i] /= 2; - } - - if (_log.shouldLog(Log.WARN) ) { - StringBuffer buf = new StringBuffer(128); - buf.append("Degrading the tunnel throughput measurements to "); - for (int i = 0; i < THROUGHPUT_COUNT; i++) - buf.append(_peakTunnel1mThroughput[i]).append(','); - buf.append(" for ").append(_peer.toBase64()); - _log.warn(buf.toString()); + _peakTunnelThroughput[i] *= DEGRADE_FACTOR; + _peakTunnel1mThroughput[i] *= DEGRADE_FACTOR; } } _peakThroughputCurrentTotal = 0; @@ -480,34 +419,27 @@ public class PeerProfile { /** update the stats and rates (this should be called once a minute) */ public void coalesceStats() { if (!_expanded) return; - _commError.coalesceStats(); _dbIntroduction.coalesceStats(); _dbResponseTime.coalesceStats(); _receiveSize.coalesceStats(); - _sendFailureSize.coalesceStats(); _sendSuccessSize.coalesceStats(); _tunnelCreateResponseTime.coalesceStats(); _tunnelTestResponseTime.coalesceStats(); - _tunnelTestResponseTimeSlow.coalesceStats(); _dbHistory.coalesceStats(); _tunnelHistory.coalesceStats(); coalesceThroughput(); _speedValue = calculateSpeed(); - _oldSpeedValue = calculateOldSpeed(); - _reliabilityValue = calculateReliability(); _capacityValue = calculateCapacity(); _integrationValue = calculateIntegration(); _isFailing = calculateIsFailing(); if (_log.shouldLog(Log.DEBUG)) - _log.debug("Coalesced: speed [" + _speedValue + "] reliability [" + _reliabilityValue + "] capacity [" + _capacityValue + "] integration [" + _integrationValue + "] failing? [" + _isFailing + "]"); + _log.debug("Coalesced: speed [" + _speedValue + "] capacity [" + _capacityValue + "] integration [" + _integrationValue + "] failing? [" + _isFailing + "]"); } private double calculateSpeed() { return _context.speedCalculator().calc(this); } - private double calculateOldSpeed() { return _context.oldSpeedCalculator().calc(this); } - private double calculateReliability() { return _context.reliabilityCalculator().calc(this); } private double calculateCapacity() { return _context.capacityCalculator().calc(this); } private double calculateIntegration() { return _context.integrationCalculator().calc(this); } private boolean calculateIsFailing() { return _context.isFailingCalculator().calcBoolean(this); } @@ -584,7 +516,6 @@ public class PeerProfile { //profile.coalesceStats(); buf.append("Peer " + profile.getPeer().toBase64() + ":\t Speed:\t" + fmt.format(profile.calculateSpeed()) - + " Reliability:\t" + fmt.format(profile.calculateReliability()) + " Capacity:\t" + fmt.format(profile.calculateCapacity()) + " Integration:\t" + fmt.format(profile.calculateIntegration()) + " Active?\t" + profile.getIsActive() diff --git a/router/java/src/net/i2p/router/peermanager/ProfileManagerImpl.java b/router/java/src/net/i2p/router/peermanager/ProfileManagerImpl.java index 9e789f9c6e..df7691bd16 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileManagerImpl.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileManagerImpl.java @@ -50,7 +50,6 @@ public class ProfileManagerImpl implements ProfileManager { PeerProfile data = getProfile(peer); if (data == null) return; data.setLastSendFailed(_context.clock().now()); - data.getSendFailureSize().addData(0, 0); // yeah, should be a frequency... } /** @@ -61,7 +60,6 @@ public class ProfileManagerImpl implements ProfileManager { PeerProfile data = getProfile(peer); if (data == null) return; data.setLastSendFailed(_context.clock().now()); - data.getSendFailureSize().addData(0, 0); // yeah, should be a frequency... } /** @@ -74,8 +72,6 @@ public class ProfileManagerImpl implements ProfileManager { PeerProfile data = getProfile(peer); if (data == null) return; data.setLastSendFailed(_context.clock().now()); - data.getSendFailureSize().addData(1, 0); // yeah, should be a frequency... - data.getCommError().addData(1, 0); // see above } /** @@ -125,8 +121,6 @@ public class ProfileManagerImpl implements ProfileManager { if (data == null) return; data.updateTunnelTestTimeAverage(responseTimeMs); data.getTunnelTestResponseTime().addData(responseTimeMs, responseTimeMs); - if (responseTimeMs > getSlowThreshold()) - data.getTunnelTestResponseTimeSlow().addData(responseTimeMs, responseTimeMs); } public void tunnelDataPushed(Hash peer, long rtt, int size) { diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index b090d6cc78..1a42a244c6 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -1028,7 +1028,7 @@ public class ProfileOrganizer { /** * called after locking the reorganizeLock, place the profile in the appropriate tier. - * This is where we implement the (betterThanAverage ? goToPierX : goToPierY) algorithms + * This is where we implement the (betterThanAverage ? goToTierX : goToTierY) algorithms * */ private void locked_placeProfile(PeerProfile profile) { @@ -1153,7 +1153,6 @@ public class ProfileOrganizer { organizer.isHighCapacity(peer) ? "IR " : organizer.isFailing(peer) ? "IX " : "I ") + "]: " + "\t Speed:\t" + fmt.format(profile.getSpeedValue()) - + " Reliability:\t" + fmt.format(profile.getReliabilityValue()) + " Capacity:\t" + fmt.format(profile.getCapacityValue()) + " Integration:\t" + fmt.format(profile.getIntegrationValue()) + " Active?\t" + profile.getIsActive() @@ -1164,7 +1163,6 @@ public class ProfileOrganizer { organizer.isHighCapacity(peer) ? "R " : organizer.isFailing(peer) ? "X " : " ") + "]: " + "\t Speed:\t" + fmt.format(profile.getSpeedValue()) - + " Reliability:\t" + fmt.format(profile.getReliabilityValue()) + " Capacity:\t" + fmt.format(profile.getCapacityValue()) + " Integration:\t" + fmt.format(profile.getIntegrationValue()) + " Active?\t" + profile.getIsActive() diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java index fd88c406bb..4f5d236116 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java +++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java @@ -95,7 +95,6 @@ class ProfilePersistenceHelper { if (_us != null) buf.append("# as calculated by ").append(_us.toBase64()).append(NL); buf.append("#").append(NL); - buf.append("# reliability: ").append(profile.getReliabilityValue()).append(NL); buf.append("# capacity: ").append(profile.getCapacityValue()).append(NL); buf.append("# integration: ").append(profile.getIntegrationValue()).append(NL); buf.append("# speedValue: ").append(profile.getSpeedValue()).append(NL); @@ -134,15 +133,12 @@ class ProfilePersistenceHelper { if (profile.getIsExpanded()) { // only write out expanded data if, uh, we've got it - profile.getCommError().store(out, "commError"); profile.getDbIntroduction().store(out, "dbIntroduction"); profile.getDbResponseTime().store(out, "dbResponseTime"); profile.getReceiveSize().store(out, "receiveSize"); - profile.getSendFailureSize().store(out, "sendFailureSize"); profile.getSendSuccessSize().store(out, "sendSuccessSize"); profile.getTunnelCreateResponseTime().store(out, "tunnelCreateResponseTime"); profile.getTunnelTestResponseTime().store(out, "tunnelTestResponseTime"); - profile.getTunnelTestResponseTimeSlow().store(out, "tunnelTestResponseTimeSlow"); } } @@ -217,15 +213,12 @@ class ProfilePersistenceHelper { profile.getTunnelHistory().load(props); profile.getDBHistory().load(props); - profile.getCommError().load(props, "commError", true); profile.getDbIntroduction().load(props, "dbIntroduction", true); profile.getDbResponseTime().load(props, "dbResponseTime", true); profile.getReceiveSize().load(props, "receiveSize", true); - profile.getSendFailureSize().load(props, "sendFailureSize", true); profile.getSendSuccessSize().load(props, "sendSuccessSize", true); profile.getTunnelCreateResponseTime().load(props, "tunnelCreateResponseTime", true); profile.getTunnelTestResponseTime().load(props, "tunnelTestResponseTime", true); - profile.getTunnelTestResponseTimeSlow().load(props, "tunnelTestResponseTimeSlow", true); if (_log.shouldLog(Log.DEBUG)) _log.debug("Loaded the profile for " + peer.toBase64() + " from " + file.getName()); diff --git a/router/java/src/net/i2p/router/peermanager/ReliabilityCalculator.java b/router/java/src/net/i2p/router/peermanager/ReliabilityCalculator.java deleted file mode 100644 index 674b2fd915..0000000000 --- a/router/java/src/net/i2p/router/peermanager/ReliabilityCalculator.java +++ /dev/null @@ -1,91 +0,0 @@ -package net.i2p.router.peermanager; - -import net.i2p.router.RouterContext; -import net.i2p.stat.RateStat; -import net.i2p.util.Log; - -/** - * Determine how reliable the peer is - how likely they'll be able to respond or - * otherwise carry out whatever we ask them to (or even merely be reachable) - * - */ -public class ReliabilityCalculator extends Calculator { - private Log _log; - private RouterContext _context; - - public ReliabilityCalculator(RouterContext context) { - _context = context; - _log = context.logManager().getLog(ReliabilityCalculator.class); - } - - public double calc(PeerProfile profile) { - // if we've never succeeded (even if we've never tried), the reliability is zip - if (profile.getSendSuccessSize().getRate(60*60*1000).getLifetimeEventCount() < 0) - return profile.getReliabilityBonus(); - - long val = 0; - val += profile.getSendSuccessSize().getRate(60*1000).getCurrentEventCount() * 20; - val += profile.getSendSuccessSize().getRate(60*1000).getLastEventCount() * 10; - val += profile.getSendSuccessSize().getRate(60*60*1000).getLastEventCount() * 1; - val += profile.getSendSuccessSize().getRate(60*60*1000).getCurrentEventCount() * 5; - - val += profile.getTunnelCreateResponseTime().getRate(10*60*1000).getLastEventCount() * 5; - val += profile.getTunnelCreateResponseTime().getRate(60*60*1000).getCurrentEventCount(); - val += profile.getTunnelCreateResponseTime().getRate(60*60*1000).getLastEventCount(); - - //val -= profile.getSendFailureSize().getRate(60*1000).getLastEventCount() * 5; - //val -= profile.getSendFailureSize().getRate(60*60*1000).getCurrentEventCount()*2; - //val -= profile.getSendFailureSize().getRate(60*60*1000).getLastEventCount()*2; - - RateStat rejRate = profile.getTunnelHistory().getRejectionRate(); - if (rejRate.getRate(60*1000).getCurrentEventCount() > 0) - val -= 200; - if (rejRate.getRate(60*1000).getLastEventCount() > 0) - val -= 100; - if (rejRate.getRate(10*60*1000).getCurrentEventCount() > 0) - val -= 10; - if (rejRate.getRate(10*60*1000).getCurrentEventCount() > 0) - val -= 5; - - // penalize them heavily for dropping netDb requests (though these could have - // failed due to tunnel timeouts, so don't be too mean) - if (profile.getDBHistory().getFailedLookupRate().getRate(60*1000).getCurrentEventCount() > 0) - val -= 10; - if (profile.getDBHistory().getFailedLookupRate().getRate(60*1000).getLastEventCount() > 0) - val -= 5; - - // scream and shout on network errors - if (profile.getCommError().getRate(60*1000).getCurrentEventCount() > 0) - val -= 200; - if (profile.getCommError().getRate(60*1000).getLastEventCount() > 0) - val -= 200; - - if (profile.getCommError().getRate(60*60*1000).getCurrentEventCount() > 0) - val -= 10; - if (profile.getCommError().getRate(60*60*1000).getLastEventCount() > 0) - val -= 10; - - val -= profile.getCommError().getRate(24*60*60*1000).getCurrentEventCount() * 1; - - //long now = _context.clock().now(); - - long timeSinceRejection = 61*60*1000; // now - profile.getTunnelHistory().getLastRejected(); - if (timeSinceRejection > 60*60*1000) { - // noop. rejection was over 60 minutes ago - } else if (timeSinceRejection > 10*60*1000) { - val -= 10; // 10-60 minutes ago we got a rejection - } else if (timeSinceRejection > 60*1000) { - val -= 50; // 1-10 minutes ago we got a rejection - } else { - val -= 100; // we got a rejection within the last minute - } - - //if ( (profile.getLastSendSuccessful() > 0) && (now - 24*60*60*1000 > profile.getLastSendSuccessful()) ) { - // // we know they're real, but we havent sent them a message successfully in over a day. - // val -= 1000; - //} - - val += profile.getReliabilityBonus(); - return val; - } -} diff --git a/router/java/src/net/i2p/router/peermanager/StrictSpeedCalculator.java b/router/java/src/net/i2p/router/peermanager/StrictSpeedCalculator.java deleted file mode 100644 index 99453b0abf..0000000000 --- a/router/java/src/net/i2p/router/peermanager/StrictSpeedCalculator.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.i2p.router.peermanager; - -import net.i2p.router.RouterContext; -import net.i2p.stat.Rate; -import net.i2p.stat.RateStat; -import net.i2p.util.Log; - -/** - * Simple speed calculator that just counts how many messages go through the - * tunnel. - * - */ -public class StrictSpeedCalculator extends Calculator { - private Log _log; - private RouterContext _context; - - public StrictSpeedCalculator(RouterContext context) { - _context = context; - _log = context.logManager().getLog(StrictSpeedCalculator.class); - } - - public double calc(PeerProfile profile) { - return countSuccesses(profile); - } - private double countSuccesses(PeerProfile profile) { - RateStat success = profile.getTunnelHistory().getProcessSuccessRate(); - RateStat failure = profile.getTunnelHistory().getProcessFailureRate(); - return messagesPerMinute(success, failure); - } - private double messagesPerMinute(RateStat success, RateStat failure) { - double rv = 0.0d; - if (success != null) { - Rate rate = null; - long periods[] = success.getPeriods(); - for (int i = 0; i < periods.length; i++) { - rate = success.getRate(periods[i]); - if ( (rate != null) && (rate.getCurrentTotalValue() > 0) ) - break; - } - - double value = rate.getCurrentTotalValue(); - value += rate.getLastTotalValue(); - rv = value * 10.0d * 60.0d * 1000.0d / (double)rate.getPeriod(); - - // if any of the messages are getting fragmented and cannot be - // handled, penalize like crazy - Rate fail = failure.getRate(rate.getPeriod()); - if (fail.getCurrentTotalValue() > 0) - rv /= fail.getCurrentTotalValue(); - } - return rv; - } - - /* - public double calc(PeerProfile profile) { - double successCount = countSuccesses(profile); - double failureCount = countFailures(profile); - - double rv = successCount - 5*failureCount; - if (rv < 0) - rv = 0; - return rv; - } - private double countSuccesses(PeerProfile profile) { - RateStat success = profile.getTunnelHistory().getProcessSuccessRate(); - return messagesPerMinute(success); - } - private double countFailures(PeerProfile profile) { - RateStat failure = profile.getTunnelHistory().getProcessFailureRate(); - return messagesPerMinute(failure); - } - private double messagesPerMinute(RateStat stat) { - double rv = 0.0d; - if (stat != null) { - Rate rate = null; - long periods[] = stat.getPeriods(); - for (int i = 0; i < periods.length; i++) { - rate = stat.getRate(periods[i]); - if ( (rate != null) && (rate.getCurrentTotalValue() > 0) ) - break; - } - - double value = rate.getCurrentTotalValue(); - value += rate.getLastTotalValue(); - rv = value * 60.0d * 1000.0d / (double)rate.getPeriod(); - } - return rv; - } - */ -} diff --git a/router/java/src/net/i2p/router/peermanager/TunnelHistory.java b/router/java/src/net/i2p/router/peermanager/TunnelHistory.java index 2ea23099dd..d43aff6cd4 100644 --- a/router/java/src/net/i2p/router/peermanager/TunnelHistory.java +++ b/router/java/src/net/i2p/router/peermanager/TunnelHistory.java @@ -26,8 +26,6 @@ public class TunnelHistory { private volatile long _lastFailed; private RateStat _rejectRate; private RateStat _failRate; - private RateStat _processSuccessRate; - private RateStat _processFailureRate; private String _statGroup; /** probabalistic tunnel rejection due to a flood of requests */ @@ -47,14 +45,10 @@ public class TunnelHistory { } private void createRates(String statGroup) { - _rejectRate = new RateStat("tunnelHistory.rejectRate", "How often does this peer reject a tunnel request?", statGroup, new long[] { 60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _failRate = new RateStat("tunnelHistory.failRate", "How often do tunnels this peer accepts fail?", statGroup, new long[] { 60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _processSuccessRate = new RateStat("tunnelHistory.processSuccessRate", "How many messages does a tunnel process?", statGroup, new long[] { 5*60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _processFailureRate = new RateStat("tunnelHistory.processfailureRate", "How many messages does a tunnel fail?", statGroup, new long[] { 5*60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l }); + _rejectRate = new RateStat("tunnelHistory.rejectRate", "How often does this peer reject a tunnel request?", statGroup, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l }); + _failRate = new RateStat("tunnelHistory.failRate", "How often do tunnels this peer accepts fail?", statGroup, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l }); _rejectRate.setStatLog(_context.statManager().getStatLog()); _failRate.setStatLog(_context.statManager().getStatLog()); - _processSuccessRate.setStatLog(_context.statManager().getStatLog()); - _processFailureRate.setStatLog(_context.statManager().getStatLog()); } /** total tunnels the peer has agreed to participate in */ @@ -77,10 +71,7 @@ public class TunnelHistory { public long getLastFailed() { return _lastFailed; } public void incrementProcessed(int processedSuccessfully, int failedProcessing) { - if (processedSuccessfully > 0) - _processSuccessRate.addData(processedSuccessfully, 0); - if (failedProcessing > 0) - _processFailureRate.addData(failedProcessing, 0); + // old strict speed calculator } public void incrementAgreedTo() { @@ -129,16 +120,12 @@ public class TunnelHistory { public RateStat getRejectionRate() { return _rejectRate; } public RateStat getFailedRate() { return _failRate; } - public RateStat getProcessSuccessRate() { return _processSuccessRate; } - public RateStat getProcessFailureRate() { return _processFailureRate; } public void coalesceStats() { if (_log.shouldLog(Log.DEBUG)) _log.debug("Coallescing stats"); _rejectRate.coalesceStats(); _failRate.coalesceStats(); - _processFailureRate.coalesceStats(); - _processSuccessRate.coalesceStats(); } private final static String NL = System.getProperty("line.separator"); @@ -161,8 +148,6 @@ public class TunnelHistory { out.write(buf.toString().getBytes()); _rejectRate.store(out, "tunnelHistory.rejectRate"); _failRate.store(out, "tunnelHistory.failRate"); - _processSuccessRate.store(out, "tunnelHistory.processSuccessRate"); - _processFailureRate.store(out, "tunnelHistory.processFailureRate"); } private void add(StringBuffer buf, String name, long val, String description) { @@ -187,12 +172,6 @@ public class TunnelHistory { _failRate.load(props, "tunnelHistory.failRate", true); if (_log.shouldLog(Log.DEBUG)) _log.debug("Loading tunnelHistory.failRate"); - _processFailureRate.load(props, "tunnelHistory.processFailureRate", true); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Loading tunnelHistory.processFailureRate"); - _processSuccessRate.load(props, "tunnelHistory.processSuccessRate", true); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Loading tunnelHistory.processSuccessRate"); } catch (IllegalArgumentException iae) { _log.warn("TunnelHistory rates are corrupt, resetting", iae); createRates(_statGroup); From 49c7fc30c09ab42ce4741d2177d65e54bc461e99 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 19:01:26 +0000 Subject: [PATCH 066/112] cleanup --- apps/i2ptunnel/jsp/editServer.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 59a7b9cf7e..3a958fd42e 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -268,7 +268,7 @@
    From 3a1218283846c44fbc06591aba97dc9089110178 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 20:33:54 +0000 Subject: [PATCH 067/112] Transport: - Maintain a router hash -> IP map in transport, to support additional IP checks (unused for now) - Catch error on pre-2.6 kernels - Some concurrent conversion - Fix an HTML error on peers.jsp --- .../transport/CommSystemFacadeImpl.java | 4 +++ .../i2p/router/transport/TransportImpl.java | 34 ++++++++++++------- .../router/transport/TransportManager.java | 15 +++++++- .../router/transport/ntcp/EstablishState.java | 6 +++- .../router/transport/ntcp/EventPumper.java | 11 +++--- .../router/transport/ntcp/NTCPTransport.java | 7 +++- .../transport/udp/EstablishmentManager.java | 2 ++ .../router/transport/udp/UDPTransport.java | 3 ++ 8 files changed, 62 insertions(+), 20 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index c94178004c..0eafb8fa97 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -133,6 +133,10 @@ public class CommSystemFacadeImpl extends CommSystemFacade { return _manager.wasUnreachable(dest); } + public byte[] getIP(Hash dest) { + return _manager.getIP(dest); + } + public List getMostRecentErrorMessages() { return _manager.getMostRecentErrorMessages(); } diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 04a2cde914..1dffde7995 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.Hash; import net.i2p.data.RouterAddress; @@ -34,6 +35,7 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; /** @@ -47,8 +49,10 @@ public abstract class TransportImpl implements Transport { private List _sendPool; protected RouterContext _context; /** map from routerIdentHash to timestamp (Long) that the peer was last unreachable */ - private Map _unreachableEntries; - private Set _wasUnreachableEntries; + private Map _unreachableEntries; + private Set _wasUnreachableEntries; + /** global router ident -> IP */ + private static Map _IPMap = new ConcurrentHashMap(128); /** * Initialize the new transport @@ -67,7 +71,7 @@ public abstract class TransportImpl implements Transport { _context.statManager().createRateStat("transport.expiredOnQueueLifetime", "How long a message that expires on our outbound queue is processed", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } ); _sendPool = new ArrayList(16); _unreachableEntries = new HashMap(16); - _wasUnreachableEntries = new HashSet(16); + _wasUnreachableEntries = new ConcurrentHashSet(16); _currentAddress = null; } @@ -483,10 +487,8 @@ public abstract class TransportImpl implements Transport { * This is NOT reset if the peer contacts us and it is never expired. */ public boolean wasUnreachable(Hash peer) { - synchronized (_wasUnreachableEntries) { - if (_wasUnreachableEntries.contains(peer)) - return true; - } + if (_wasUnreachableEntries.contains(peer)) + return true; RouterInfo ri = _context.netDb().lookupRouterInfoLocally(peer); if (ri == null) return false; @@ -496,16 +498,22 @@ public abstract class TransportImpl implements Transport { * Maintain the WasUnreachable list */ public void markWasUnreachable(Hash peer, boolean yes) { - synchronized (_wasUnreachableEntries) { - if (yes) - _wasUnreachableEntries.add(peer); - else - _wasUnreachableEntries.remove(peer); - } + if (yes) + _wasUnreachableEntries.add(peer); + else + _wasUnreachableEntries.remove(peer); if (_log.shouldLog(Log.WARN)) _log.warn(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer); } + public static void setIP(Hash peer, byte[] ip) { + _IPMap.put(peer, ip); + } + + public static byte[] getIP(Hash peer) { + return _IPMap.get(peer); + } + public static boolean isPubliclyRoutable(byte addr[]) { if (addr.length == 4) { if ((addr[0]&0xFF) == 127) return false; diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index bade759130..112f51efae 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -33,7 +33,7 @@ import net.i2p.util.Log; public class TransportManager implements TransportEventListener { private Log _log; - private List _transports; + private List _transports; private RouterContext _context; private final static String PROP_ENABLE_UDP = "i2np.udp.enable"; @@ -229,6 +229,19 @@ public class TransportManager implements TransportEventListener { return true; } + /** + * IP of the peer from the last connection (in or out, any transport). + * This may be different from that advertised in the netDb, + * as the peer may be hidden, or connect from a different IP, or + * change his netDb later, in an attempt to avoid restrictions. + * + * For blocking purposes, etc. it's worth checking both + * the netDb addresses and this address. + */ + public byte[] getIP(Hash dest) { + return TransportImpl.getIP(dest); + } + Map getAddresses() { Map rv = new HashMap(_transports.size()); for (int i = 0; i < _transports.size(); i++) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index d18cc1ad3a..942d6cbc99 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -472,6 +472,8 @@ public class EstablishState { byte nextReadIV[] = new byte[16]; System.arraycopy(_e_bobSig, _e_bobSig.length-16, nextReadIV, 0, nextReadIV.length); _con.finishOutboundEstablishment(_dh.getSessionKey(), (_tsA-_tsB), nextWriteIV, nextReadIV); // skew in seconds + _transport.setIP(_con.getRemotePeer().calculateHash(), + _con.getChannel().socket().getInetAddress().getAddress()); return; } } @@ -546,15 +548,17 @@ public class EstablishState { Signature sig = new Signature(s); _verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey()); if (_verified) { + byte[] ip = _con.getChannel().socket().getInetAddress().getAddress(); if (_context.shitlist().isShitlistedForever(alice.calculateHash())) { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping inbound connection from permanently shitlisted peer: " + alice.calculateHash().toBase64()); // So next time we will not accept the con from this IP, // rather than doing the whole handshake - _context.blocklist().add(_con.getChannel().socket().getInetAddress().getAddress()); + _context.blocklist().add(ip); fail("Peer is shitlisted forever: " + alice.calculateHash().toBase64()); return; } + _transport.setIP(alice.calculateHash(), ip); if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "verification successful for " + _con); diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java index 9c75f53283..0c062fa6cf 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java @@ -66,22 +66,25 @@ public class EventPumper implements Runnable { public void startPumping() { if (_log.shouldLog(Log.INFO)) _log.info("Starting pumper"); - _alive = true; _wantsRead = new ArrayList(16); _wantsWrite = new ArrayList(4); _wantsRegister = new ArrayList(1); _wantsConRegister = new ArrayList(4); try { _selector = Selector.open(); + _alive = true; + new I2PThread(this, "NTCP Pumper", true).start(); } catch (IOException ioe) { - _log.error("Error opening the selector", ioe); + _log.log(Log.CRIT, "Error opening the NTCP selector", ioe); + } catch (java.lang.InternalError jlie) { + // "unable to get address of epoll functions, pre-2.6 kernel?" + _log.log(Log.CRIT, "Error opening the NTCP selector", jlie); } - new I2PThread(this, "NTCP Pumper", true).start(); } public void stopPumping() { _alive = false; - if (_selector.isOpen()) + if (_selector != null && _selector.isOpen()) _selector.wakeup(); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index c23245bae4..103b46b153 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -241,6 +241,8 @@ public class NTCPTransport extends TransportImpl { super.afterSend(msg, sendSuccessful, allowRequeue, msToSend); } public TransportBid bid(RouterInfo toAddress, long dataSize) { + if (!isAlive()) + return null; Hash peer = toAddress.getIdentity().calculateHash(); if (_context.shitlist().isShitlisted(peer, STYLE)) { // we aren't shitlisted in general (since we are trying to get a bid), but we have @@ -591,7 +593,10 @@ public class NTCPTransport extends TransportImpl { for (Iterator iter = peers.iterator(); iter.hasNext(); ) { NTCPConnection con = (NTCPConnection)iter.next(); String name = con.getRemotePeer().calculateHash().toBase64().substring(0,6); - buf.append("").append(name); + buf.append("").append(name).append(""); + //byte[] ip = getIP(con.getRemotePeer().calculateHash()); + //if (ip != null) + // buf.append(' ').append(_context.blocklist().toStr(ip)); buf.append(""); if (con.isInbound()) buf.append("in"); diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 896fe1ce43..c5b654b905 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -450,6 +450,7 @@ public class EstablishmentManager { _transport.addRemotePeerState(peer); _transport.inboundConnectionReceived(); + _transport.setIP(remote.calculateHash(), state.getSentIP()); _context.statManager().addRateData("udp.inboundEstablishTime", state.getLifetime(), 0); sendInboundComplete(peer); @@ -531,6 +532,7 @@ public class EstablishmentManager { _transport.addRemotePeerState(peer); + _transport.setIP(remote.calculateHash(), state.getSentIP()); _context.statManager().addRateData("udp.outboundEstablishTime", state.getLifetime(), 0); sendOurInfo(peer, false); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index e5185defaf..289e42ba35 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -1762,6 +1762,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority buf.append(" [shitlisted]"); appended = true; } + //byte[] ip = getIP(peer.getRemotePeer()); + //if (ip != null) + // buf.append(' ').append(_context.blocklist().toStr(ip)); buf.append(""); long idleIn = (now-peer.getLastReceiveTime())/1000; From d2fc3972955e686d61506d9295378468e60ae7cc Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 21:00:26 +0000 Subject: [PATCH 068/112] -3 --- history.txt | 22 +++++++++++++++++++ .../src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index 9ac00e87b6..789aca373f 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,25 @@ +2009-04-02 zzz + * Profiles: + - Remove unused calculators and RateStats: + CapacityCalculator, StrictSpeedCalculator, IsFailingCalculator; + sendFailureSize, processSuccessRate, processfailureRate, commErrorRate, + tunnelTestResponseTimeSlow + - Reduced number of Rates in these RateStats: + sendSuccessSize, receiveSize, rejectRate, failRate + - ~5KB/profile savings total + - Deflate speed calculation once an hour instead of once a day, + to improve fast tier selection + - Remove dup comment in persisted files + * StatisticsManager - effective in 0.7.2: + - Spoof uptime to 90m for all + - Change tunnel stats from 10m to 60m + * Transport: + - Maintain a router hash -> IP map in transport, + to support additional IP checks (unused for now) + - Catch error on pre-2.6 kernels + - Some concurrent conversion + - Fix an HTML error on peers.jsp + 2009-04-01 zzz * I2PTunnel: Fix tunnel close http://forum.i2p/viewtopic.php?t=3231 diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index a727710d68..a34e9238c0 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 2; + public final static long BUILD = 3; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 1aa7fbbba056e9484123dcb119f894f9356ef9a8 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 22:07:25 +0000 Subject: [PATCH 069/112] -3 didnt build --- router/java/src/net/i2p/router/RouterContext.java | 6 ------ router/java/src/net/i2p/router/peermanager/PeerProfile.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index e3b5e38466..ba82fb8393 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -12,7 +12,6 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.peermanager.Calculator; import net.i2p.router.peermanager.CapacityCalculator; import net.i2p.router.peermanager.IntegrationCalculator; -import net.i2p.router.peermanager.IsFailingCalculator; import net.i2p.router.peermanager.PeerManagerFacadeImpl; import net.i2p.router.peermanager.ProfileManagerImpl; import net.i2p.router.peermanager.ProfileOrganizer; @@ -60,7 +59,6 @@ public class RouterContext extends I2PAppContext { private MessageStateMonitor _messageStateMonitor; private RouterThrottle _throttle; private RouterClock _clock; - private Calculator _isFailingCalc; private Calculator _integrationCalc; private Calculator _speedCalc; private Calculator _capacityCalc; @@ -128,7 +126,6 @@ public class RouterContext extends I2PAppContext { _messageValidator = new MessageValidator(this); //_throttle = new RouterThrottleImpl(this); _throttle = new RouterDoSThrottle(this); - _isFailingCalc = new IsFailingCalculator(this); _integrationCalc = new IntegrationCalculator(this); _speedCalc = new SpeedCalculator(this); _capacityCalc = new CapacityCalculator(this); @@ -258,8 +255,6 @@ public class RouterContext extends I2PAppContext { */ public RouterThrottle throttle() { return _throttle; } - /** how do we rank the failure of profiles? */ - public Calculator isFailingCalculator() { return _isFailingCalc; } /** how do we rank the integration of profiles? */ public Calculator integrationCalculator() { return _integrationCalc; } /** how do we rank the speed of profiles? */ @@ -289,7 +284,6 @@ public class RouterContext extends I2PAppContext { buf.append(_statPublisher).append('\n'); buf.append(_shitlist).append('\n'); buf.append(_messageValidator).append('\n'); - buf.append(_isFailingCalc).append('\n'); buf.append(_integrationCalc).append('\n'); buf.append(_speedCalc).append('\n'); return buf.toString(); diff --git a/router/java/src/net/i2p/router/peermanager/PeerProfile.java b/router/java/src/net/i2p/router/peermanager/PeerProfile.java index 5c058b4938..2f311b52f9 100644 --- a/router/java/src/net/i2p/router/peermanager/PeerProfile.java +++ b/router/java/src/net/i2p/router/peermanager/PeerProfile.java @@ -442,7 +442,7 @@ public class PeerProfile { private double calculateSpeed() { return _context.speedCalculator().calc(this); } private double calculateCapacity() { return _context.capacityCalculator().calc(this); } private double calculateIntegration() { return _context.integrationCalculator().calc(this); } - private boolean calculateIsFailing() { return _context.isFailingCalculator().calcBoolean(this); } + private boolean calculateIsFailing() { return false; } void setIsFailing(boolean val) { _isFailing = val; } public int hashCode() { return (_peer == null ? 0 : _peer.hashCode()); } From c7d815b5b886131de11b1ee2244a058dd8919761 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 22:08:39 +0000 Subject: [PATCH 070/112] -4 (-3 didnt build) --- router/java/src/net/i2p/router/RouterVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index a34e9238c0..5f58c2cdd1 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 3; + public final static long BUILD = 4; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 25d5883a0e0bb88addf5b2ef445c9869ceffe110 Mon Sep 17 00:00:00 2001 From: sponge Date: Fri, 3 Apr 2009 13:31:41 +0000 Subject: [PATCH 071/112] 2009-04-03 sponge * Fix broken dependencies for BOB.jar --- apps/BOB/nbproject/project.properties | 41 ++++++++------------------- history.txt | 2 ++ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/apps/BOB/nbproject/project.properties b/apps/BOB/nbproject/project.properties index 7a94ff6ddd..ee493f7643 100644 --- a/apps/BOB/nbproject/project.properties +++ b/apps/BOB/nbproject/project.properties @@ -24,40 +24,23 @@ dist.dir=dist dist.jar=${dist.dir}/BOB.jar dist.javadoc.dir=${dist.dir}/javadoc excludes= -file.reference.build-javadoc=../../../i2p.i2p/build/javadoc -file.reference.core.jar=../i2p.i2p/core/dist/core.jar -file.reference.i2p.jar=../../bob/i2p/i2p.i2p/build/i2p.jar -file.reference.i2p.jar-1=../../build/i2p.jar -file.reference.i2p.jar-2=../i2p.i2p/core/java/build/i2p.jar -file.reference.i2p.jar-3=../../../i2p.i2p/build/i2p.jar -file.reference.i2ptunnel.jar=../../../i2p.i2p/build/i2ptunnel.jar -file.reference.java-src=../i2p.i2p/core/java/src/ -file.reference.jbigi.jar=../../bob/i2p/i2p.i2p/build/jbigi.jar -file.reference.mstreaming.jar=../../bob/i2p/i2p.i2p/build/mstreaming.jar -file.reference.mstreaming.jar-1=../../build/mstreaming.jar -file.reference.mstreaming.jar-2=../../../i2p.i2p/build/mstreaming.jar -file.reference.NetBeansProjects-i2p.i2p=../i2p.i2p/ -file.reference.router.jar=../../build/router.jar -file.reference.streaming.jar=../../bob/i2p/i2p.i2p/build/streaming.jar -file.reference.streaming.jar-1=../../../i2p.i2p/build/streaming.jar -file.reference.wrapper-freebsd=../../installer/lib/wrapper/freebsd/ -file.reference.wrapper-linux=../../installer/lib/wrapper/linux/ -file.reference.wrapper-linux64=../../installer/lib/wrapper/linux64/ -file.reference.wrapper-macosx=../../installer/lib/wrapper/macosx/ -file.reference.wrapper-solaris=../../installer/lib/wrapper/solaris/ -file.reference.wrapper-win32=../../installer/lib/wrapper/win32/ +file.reference.build-javadoc=../../i2p.i2p/build/javadoc +file.reference.i2p.jar=../../core/java/build/i2p.jar +file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar +file.reference.mstreaming.jar=../ministreaming/java/build/mstreaming.jar +file.reference.router.jar=../../router/java/build/router.jar +file.reference.streaming.jar=../streaming/java/build/streaming.jar file.reference.wrapper.jar=../../installer/lib/wrapper/linux/wrapper.jar includes=** jar.compress=false javac.classpath=\ - ${file.reference.wrapper.jar}:\ - ${file.reference.streaming.jar-1}:\ - ${file.reference.i2ptunnel.jar}:\ - ${file.reference.i2p.jar-1}:\ ${file.reference.router.jar}:\ - ${file.reference.mstreaming.jar-1}:\ - ${file.reference.mstreaming.jar-2}:\ - ${file.reference.i2p.jar-3} + ${file.reference.i2ptunnel.jar}:\ + ${file.reference.mstreaming.jar}:\ + ${file.reference.streaming.jar}:\ + ${file.reference.wrapper.jar}:\ + ${file.reference.i2p.jar}:\ + ${file.reference.router.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/history.txt b/history.txt index 789aca373f..a7bfb78b57 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,5 @@ +2009-04-03 sponge + * Fix broken dependencies for BOB.jar 2009-04-02 zzz * Profiles: - Remove unused calculators and RateStats: From 0c7cb9d78197a355784c60d595a9a76023bb1a58 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Apr 2009 14:26:48 +0000 Subject: [PATCH 072/112] put java version on logs.jsp --- apps/routerconsole/jsp/logs.jsp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp index 6bfc9c4695..36d3562fe0 100644 --- a/apps/routerconsole/jsp/logs.jsp +++ b/apps/routerconsole/jsp/logs.jsp @@ -21,6 +21,12 @@

    Service (Wrapper) logs:

    +
    +

    Java Version:

    +
    +<%=System.getProperty("java.vendor")%> <%=System.getProperty("java.version")%>
    +<%=System.getProperty("os.name")%> <%=System.getProperty("os.arch")%> <%=System.getProperty("os.version")%>
    + 
    From e5b1450e83c44c6d4fc79af7ff55c77974816089 Mon Sep 17 00:00:00 2001 From: sponge Date: Fri, 3 Apr 2009 15:33:51 +0000 Subject: [PATCH 073/112] 2009-04-03 sponge * Router build version incremented to 5 now that the build succeeds. --- history.txt | 1 + router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index a7bfb78b57..283be6c21e 100644 --- a/history.txt +++ b/history.txt @@ -1,5 +1,6 @@ 2009-04-03 sponge * Fix broken dependencies for BOB.jar + * Router build version incremented to 5. 2009-04-02 zzz * Profiles: - Remove unused calculators and RateStats: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 5f58c2cdd1..5203f2360b 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 4; + public final static long BUILD = 5; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From bb0531053dca4abe57978996b6c17ece81a8834d Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Apr 2009 21:33:35 +0000 Subject: [PATCH 074/112] Console: Fix bug with IE buttons not working, because it sends the label instead of the value --- .../java/src/net/i2p/i2ptunnel/web/IndexBean.java | 2 +- .../src/net/i2p/router/web/ConfigClientsHandler.java | 12 ++++++++++++ .../src/net/i2p/router/web/ConfigClientsHelper.java | 6 +++--- .../src/net/i2p/router/web/ConfigRestartBean.java | 11 ++++++----- apps/routerconsole/jsp/configclients.jsp | 5 +++++ 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index fcf45d94d3..ce547cd029 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -142,7 +142,7 @@ public class IndexBean { } private String processAction() { - if ( (_action == null) || (_action.trim().length() <= 0) ) + if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action))) return ""; if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) ) return "Invalid nonce, are you being spoofed?"; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java index 7af6125d65..b612c005b2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java @@ -41,6 +41,18 @@ public class ConfigClientsHandler extends FormHandler { startClient(appnum); else startWebApp(app); + } else if (_action.toLowerCase().startsWith("start ") && + _action.toLowerCase().endsWith("")) { + // IE sucks + String app = _action.substring(23, _action.length() - 7); + int appnum = -1; + try { + appnum = Integer.parseInt(app); + } catch (NumberFormatException nfe) {} + if (appnum >= 0) + startClient(appnum); + else + startWebApp(app); } else { addFormError("Unsupported " + _action); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index 2bee435335..984569b967 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -62,10 +62,10 @@ public class ConfigClientsHelper extends HelperBase { if (ro) buf.append("disabled=\"true\" "); } - buf.append("/> "); + buf.append("/> "); if (!enabled) { - buf.append(""); + buf.append(""); } - buf.append(" ").append(desc).append("\n"); + buf.append(" ").append(desc).append("\n"); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java index e8eb6b26d3..7ae7181bc2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java @@ -24,20 +24,21 @@ public class ConfigRestartBean { RouterContext ctx = ContextHelper.getContext(null); String systemNonce = getNonce(); if ( (nonce != null) && (systemNonce.equals(nonce)) && (action != null) ) { - if ("shutdownImmediate".equals(action)) { + // Normal browsers send value, IE sends button label + if ("shutdownImmediate".equals(action) || "Shutdown immediately".equals(action)) { 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)) { + } else if ("cancelShutdown".equals(action) || "Cancel shutdown".equals(action)) { ctx.router().cancelGracefulShutdown(); - } else if ("restartImmediate".equals(action)) { + } else if ("restartImmediate".equals(action) || "Restart immediately".equals(action)) { 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)) { + } else if ("restart".equalsIgnoreCase(action)) { ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART)); ctx.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); - } else if ("shutdown".equals(action)) { + } else if ("shutdown".equalsIgnoreCase(action)) { ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL)); ctx.router().shutdownGracefully(); } diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp index 7240281988..8e66cb54ee 100644 --- a/apps/routerconsole/jsp/configclients.jsp +++ b/apps/routerconsole/jsp/configclients.jsp @@ -5,6 +5,11 @@ I2P Router Console - config clients + <%@include file="nav.jsp" %> From 8de560981709863847cfc90755de697a3d56dc40 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Apr 2009 21:42:48 +0000 Subject: [PATCH 075/112] * Update: - Change default to "Download and verify" - Change news fetch default to 24h (was 12h) --- .../src/net/i2p/router/web/ConfigUpdateHandler.java | 4 ++-- .../java/src/net/i2p/router/web/UpdateHandler.java | 2 ++ core/java/src/net/i2p/crypto/TrustedUpdate.java | 13 +++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index 818b748a7b..3c6e1692c7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -21,9 +21,9 @@ public class ConfigUpdateHandler extends FormHandler { // public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD"; public static final String DEFAULT_NEWS_URL = "http://complication.i2p/news.xml"; public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency"; - public static final String DEFAULT_REFRESH_FREQUENCY = 12*60*60*1000 + ""; + public static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + ""; public static final String PROP_UPDATE_POLICY = "router.updatePolicy"; - public static final String DEFAULT_UPDATE_POLICY = "notify"; + public static final String DEFAULT_UPDATE_POLICY = "download"; public static final String PROP_SHOULD_PROXY = "router.updateThroughProxy"; public static final String DEFAULT_SHOULD_PROXY = Boolean.TRUE.toString(); public static final String PROP_PROXY_HOST = "router.updateProxyHost"; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java index 83495f33e1..81f8d6e8d9 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java @@ -166,6 +166,8 @@ public class UpdateHandler { } else { _log.log(Log.CRIT, "Update was VERIFIED, will be installed at next restart"); _status = "Update downloaded
    Click Restart to Install"; + if (up.newVersion() != null) + _status += " Version " + up.newVersion(); } } else { err = err + " from " + url; diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java index dfd055382d..06e37544b3 100644 --- a/core/java/src/net/i2p/crypto/TrustedUpdate.java +++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java @@ -108,6 +108,7 @@ D8usM7Dxp5yrDrCYZ5AIijc= private Log _log; private ArrayList _trustedKeys; + private String _newVersion; /** * Constructs a new TrustedUpdate with the default global @@ -127,6 +128,7 @@ D8usM7Dxp5yrDrCYZ5AIijc= _context = context; _log = _context.logManager().getLog(TrustedUpdate.class); _trustedKeys = new ArrayList(); + _newVersion = null; String propertyTrustedKeys = context.getProperty(PROP_TRUSTED_KEYS); @@ -379,6 +381,11 @@ D8usM7Dxp5yrDrCYZ5AIijc= } } + /** version in the .sud file, valid only after calling migrateVerified() */ + public String newVersion() { + return _newVersion; + } + /** * Verifies that the version of the given signed update file is newer than * currentVersion. @@ -390,10 +397,8 @@ D8usM7Dxp5yrDrCYZ5AIijc= * than the current version, otherwise false. */ public boolean isUpdatedVersion(String currentVersion, String signedFile) { - if (needsUpdate(currentVersion, getVersionString(signedFile))) - return true; - else - return false; + _newVersion = getVersionString(signedFile); + return needsUpdate(currentVersion, getVersionString(signedFile)); } /** From fe9b891b37d8775ffb95a7f58e4c4cdaa6649149 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Apr 2009 21:46:10 +0000 Subject: [PATCH 076/112] -6 --- history.txt | 10 ++++++++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index 283be6c21e..b4ed774806 100644 --- a/history.txt +++ b/history.txt @@ -1,6 +1,16 @@ +2009-04-03 zzz + * Console: + - Fix bug with IE buttons not working, + because it sends the label instead of the value + - Display version of downloaded update + * Update: + - Change default to "Download and verify" + - Change news fetch default to 24h (was 12h) + 2009-04-03 sponge * Fix broken dependencies for BOB.jar * Router build version incremented to 5. + 2009-04-02 zzz * Profiles: - Remove unused calculators and RateStats: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 5203f2360b..c089489691 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 5; + public final static long BUILD = 6; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 3dd5950bd1bf2891d04e3294b20a09dbf17a3793 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 4 Apr 2009 17:08:20 +0000 Subject: [PATCH 077/112] Don't let NTCP bid on msgs too big to handle --- .../net/i2p/router/transport/ntcp/NTCPConnection.java | 9 +++++++++ .../src/net/i2p/router/transport/ntcp/NTCPTransport.java | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 5610d62e11..070caae84c 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -104,6 +104,15 @@ public class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { private static final int META_FREQUENCY = 10*60*1000; private static final int INFO_FREQUENCY = 6*60*60*1000; + /** + * Why this is 16K, and where it is documented, good question? + * We claim we can do 32K datagrams so this is a problem. + * Needs to be fixed. But SSU can handle it? + * In the meantime, don't let the transport bid on big messages. + */ + public static final int BUFFER_SIZE = 16*1024; + /** 2 bytes for length and 4 for CRC */ + public static final int MAX_MSG_SIZE = BUFFER_SIZE - (2 + 4); /** * Create an inbound connected (though not established) NTCP connection diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 103b46b153..a9208e9a65 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -105,6 +105,7 @@ public class NTCPTransport extends TransportImpl { _context.statManager().createRateStat("ntcp.outboundEstablishFailed", "", "ntcp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("ntcp.outboundFailedIOEImmediate", "", "ntcp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("ntcp.invalidOutboundSkew", "", "ntcp", new long[] { 60*1000, 10*60*1000 }); + _context.statManager().createRateStat("ntcp.noBidTooLargeI2NP", "send size", "ntcp", new long[] { 60*60*1000 }); _context.statManager().createRateStat("ntcp.prepBufCache", "", "ntcp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("ntcp.queuedRecv", "", "ntcp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("ntcp.read", "", "ntcp", new long[] { 60*1000, 10*60*1000 }); @@ -243,6 +244,11 @@ public class NTCPTransport extends TransportImpl { public TransportBid bid(RouterInfo toAddress, long dataSize) { if (!isAlive()) return null; + if (dataSize > NTCPConnection.MAX_MSG_SIZE) { + // let SSU deal with it + _context.statManager().addRateData("ntcp.noBidTooLargeI2NP", dataSize, 0); + return null; + } Hash peer = toAddress.getIdentity().calculateHash(); if (_context.shitlist().isShitlisted(peer, STYLE)) { // we aren't shitlisted in general (since we are trying to get a bid), but we have From a4b5c63702e04fa1799d6abf0a20efe3873d7995 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 4 Apr 2009 17:17:34 +0000 Subject: [PATCH 078/112] -7 --- history.txt | 3 +++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index b4ed774806..acc8a79b0f 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,6 @@ +2009-04-04 zzz + * NTCP: Don't bid on messages too big to handle + 2009-04-03 zzz * Console: - Fix bug with IE buttons not working, diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c089489691..d1e87948b1 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 6; + public final static long BUILD = 7; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From bd489cf4391d10bbd238cc8a640be64d6c43b252 Mon Sep 17 00:00:00 2001 From: sponge Date: Sat, 4 Apr 2009 19:47:36 +0000 Subject: [PATCH 079/112] 2009-04-04 sponge * Hopeful fixups to the infamous orpahned tunnel problem. * BOB now 0.0.5 --- apps/BOB/nbproject/project.properties | 1 + apps/BOB/src/net/i2p/BOB/BOB.java | 3 +- apps/BOB/src/net/i2p/BOB/DoCMDS.java | 2 +- apps/BOB/src/net/i2p/BOB/I2Plistener.java | 110 +++++++---- apps/BOB/src/net/i2p/BOB/I2PtoTCP.java | 4 +- apps/BOB/src/net/i2p/BOB/MUXlisten.java | 14 +- apps/BOB/src/net/i2p/BOB/Main.java | 5 +- apps/BOB/src/net/i2p/BOB/TCPio.java | 21 +- apps/BOB/src/net/i2p/BOB/TCPlistener.java | 180 ++++++++++++------ apps/BOB/src/net/i2p/BOB/TCPtoI2P.java | 21 +- .../src/net/i2p/router/RouterVersion.java | 2 +- 11 files changed, 236 insertions(+), 127 deletions(-) diff --git a/apps/BOB/nbproject/project.properties b/apps/BOB/nbproject/project.properties index ee493f7643..9eeffd29f1 100644 --- a/apps/BOB/nbproject/project.properties +++ b/apps/BOB/nbproject/project.properties @@ -27,6 +27,7 @@ excludes= file.reference.build-javadoc=../../i2p.i2p/build/javadoc file.reference.i2p.jar=../../core/java/build/i2p.jar file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar +file.reference.jbigi.jar=../../installer/lib/jbigi/jbigi.jar file.reference.mstreaming.jar=../ministreaming/java/build/mstreaming.jar file.reference.router.jar=../../router/java/build/router.jar file.reference.streaming.jar=../streaming/java/build/streaming.jar diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java index 59b46b8d74..48a0e067c6 100644 --- a/apps/BOB/src/net/i2p/BOB/BOB.java +++ b/apps/BOB/src/net/i2p/BOB/BOB.java @@ -34,6 +34,7 @@ import java.util.Properties; import net.i2p.client.I2PClient; import net.i2p.client.streaming.RetransmissionTimer; import net.i2p.util.Log; + /** * * ################################################################################
    @@ -157,12 +158,12 @@ public class BOB { boolean save = false; // Set up all defaults to be passed forward to other threads. // Re-reading the config file in each thread is pretty damn stupid. - // I2PClient client = I2PClientFactory.createClient(); String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config"); // This is here just to ensure there is no interference with our threadgroups. RetransmissionTimer Y = RetransmissionTimer.getInstance(); i = Y.hashCode(); + { try { FileInputStream fi = new FileInputStream(configLocation); diff --git a/apps/BOB/src/net/i2p/BOB/DoCMDS.java b/apps/BOB/src/net/i2p/BOB/DoCMDS.java index 099d69feca..12d8d61560 100644 --- a/apps/BOB/src/net/i2p/BOB/DoCMDS.java +++ b/apps/BOB/src/net/i2p/BOB/DoCMDS.java @@ -46,7 +46,7 @@ public class DoCMDS implements Runnable { // FIX ME // I need a better way to do versioning, but this will do for now. - public static final String BMAJ = "00", BMIN = "00", BREV = "04", BEXT = ""; + public static final String BMAJ = "00", BMIN = "00", BREV = "05", BEXT = ""; public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT; private Socket server; private Properties props; diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java index c59683270e..813bb5d343 100644 --- a/apps/BOB/src/net/i2p/BOB/I2Plistener.java +++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java @@ -62,6 +62,26 @@ public class I2Plistener implements Runnable { tgwatch = 1; } + private void rlock() throws Exception { + database.getReadLock(); + info.getReadLock(); + } + + private void runlock() throws Exception { + database.releaseReadLock(); + info.releaseReadLock(); + } + + private void wlock() throws Exception { + database.getWriteLock(); + info.getWriteLock(); + } + + private void wunlock() throws Exception { + info.releaseWriteLock(); + database.releaseWriteLock(); + } + /** * Simply listen on I2P port, and thread connections * @@ -70,68 +90,86 @@ public class I2Plistener implements Runnable { boolean g = false; I2PSocket sessSocket = null; - serverSocket.setSoTimeout(50); - database.getReadLock(); - info.getReadLock(); - if(info.exists("INPORT")) { - tgwatch = 2; - } - info.releaseReadLock(); - database.releaseReadLock(); - boolean spin = true; - while(spin) { +die: { - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); + serverSocket.setSoTimeout(50); try { + if (info.exists("INPORT")) { + tgwatch = 2; + } + } catch (Exception e) { try { - sessSocket = serverSocket.accept(); - g = true; - } catch(ConnectException ce) { - g = false; - } catch(SocketTimeoutException ste) { - g = false; - } - if(g) { - g = false; - // toss the connection to a new thread. - I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database); - Thread t = new Thread(conn_c, "BOBI2PtoTCP"); - t.start(); + runlock(); + } catch (Exception e2) { + break die; } + break die; + } + boolean spin = true; + while (spin) { - } catch(I2PException e) { - // System.out.println("Exception " + e); + try { + rlock(); + } catch (Exception e) { + break die; + } + try { + spin = info.get("RUNNING").equals(Boolean.TRUE); + } catch (Exception e) { + try { + runlock(); + } catch (Exception e2) { + break die; + } + break die; + } + try { + try { + sessSocket = serverSocket.accept(); + g = true; + } catch (ConnectException ce) { + g = false; + } catch (SocketTimeoutException ste) { + g = false; + } + if (g) { + g = false; + // toss the connection to a new thread. + I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database); + Thread t = new Thread(conn_c, "BOBI2PtoTCP"); + t.start(); + } + + } catch (I2PException e) { + // System.out.println("Exception " + e); + } } } // System.out.println("I2Plistener: Close"); try { serverSocket.close(); - } catch(I2PException e) { + } catch (I2PException e) { // nop } // need to kill off the socket manager too. I2PSession session = socketManager.getSession(); - if(session != null) { + if (session != null) { // System.out.println("I2Plistener: destroySession"); try { session.destroySession(); - } catch(I2PSessionException ex) { + } catch (I2PSessionException ex) { // nop } } // System.out.println("I2Plistener: Waiting for children"); - while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish + while (Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish try { Thread.sleep(100); //sleep for 100 ms (One tenth second) - } catch(Exception e) { + } catch (Exception e) { // nop } } - // System.out.println("I2Plistener: Done."); + // System.out.println("I2Plistener: Done."); } } diff --git a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java index 5d24e19d36..06c3131fee 100644 --- a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java +++ b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java @@ -105,8 +105,8 @@ die: { out.flush(); // not really needed, but... } // setup to cross the streams - TCPio conn_c = new TCPio(in, Iout, info, database); // app -> I2P - TCPio conn_a = new TCPio(Iin, out, info, database); // I2P -> app + TCPio conn_c = new TCPio(in, Iout /*, info, database */ ); // app -> I2P + TCPio conn_a = new TCPio(Iin, out /* , info, database */); // I2P -> app Thread t = new Thread(conn_c, "TCPioA"); Thread q = new Thread(conn_a, "TCPioB"); // Fire! diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java index 89ab53fe62..879cf9a64a 100644 --- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java +++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java @@ -100,7 +100,7 @@ public class MUXlisten implements Runnable { // Everything is OK as far as we can tell. this.database.getWriteLock(); this.info.getWriteLock(); - this.info.add("STARTING", Boolean.TRUE); + this.info.add("STARTING", new Boolean(true)); this.info.releaseWriteLock(); this.database.releaseWriteLock(); } @@ -134,8 +134,8 @@ public class MUXlisten implements Runnable { try { wlock(); try { - info.add("RUNNING", Boolean.TRUE); - info.add("STARTING", Boolean.FALSE); + info.add("RUNNING", new Boolean(true)); + info.add("STARTING", new Boolean(false)); } catch(Exception e) { wunlock(); return; @@ -198,7 +198,7 @@ die: { try { wlock(); try { - info.add("RUNNING", Boolean.FALSE); + info.add("RUNNING", new Boolean(false)); } catch(Exception e) { wunlock(); break die; @@ -255,9 +255,9 @@ die: { try { wlock(); try { - info.add("STARTING", Boolean.FALSE); - info.add("STOPPING", Boolean.FALSE); - info.add("RUNNING", Boolean.FALSE); + info.add("STARTING", new Boolean(false)); + info.add("STOPPING", new Boolean(false)); + info.add("RUNNING", new Boolean(false)); } catch(Exception e) { wunlock(); return; diff --git a/apps/BOB/src/net/i2p/BOB/Main.java b/apps/BOB/src/net/i2p/BOB/Main.java index 2d81fb30ed..b32e4758ef 100644 --- a/apps/BOB/src/net/i2p/BOB/Main.java +++ b/apps/BOB/src/net/i2p/BOB/Main.java @@ -24,7 +24,7 @@ package net.i2p.BOB; import net.i2p.client.streaming.RetransmissionTimer; - +import net.i2p.util.SimpleScheduler; /** * Start from command line * @@ -39,6 +39,9 @@ public class Main { public static void main(String[] args) { // THINK THINK THINK THINK THINK THINK RetransmissionTimer Y = RetransmissionTimer.getInstance(); + // needs SimpleScheduler + // no way to stop the scheduler?!? + SimpleScheduler.getInstance(); BOB.main(args); Y.stop(); } diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java index c9f4ab64cd..109d8e8cb5 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPio.java +++ b/apps/BOB/src/net/i2p/BOB/TCPio.java @@ -35,7 +35,7 @@ public class TCPio implements Runnable { private InputStream Ain; private OutputStream Aout; - private NamedDB info, database; + // private NamedDB info, database; /** * Constructor @@ -43,13 +43,14 @@ public class TCPio implements Runnable { * @param Ain * @param Aout * @param info - * @param database + * + * param database */ - TCPio(InputStream Ain, OutputStream Aout, NamedDB info, NamedDB database) { + TCPio(InputStream Ain, OutputStream Aout /*, NamedDB info , NamedDB database */) { this.Ain = Ain; this.Aout = Aout; - this.info = info; - this.database = database; + // this.info = info; + // this.database = database; } /** @@ -86,11 +87,11 @@ public class TCPio implements Runnable { boolean spin = true; try { while(spin) { - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); + // database.getReadLock(); + // info.getReadLock(); + // spin = info.get("RUNNING").equals(Boolean.TRUE); + // info.releaseReadLock(); + // database.releaseReadLock(); b = Ain.read(a, 0, 1); // System.out.println(info.get("NICKNAME").toString() + " " + b); if(b > 0) { diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java index 30380a55dd..7e931768c9 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPlistener.java +++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java @@ -63,6 +63,26 @@ public class TCPlistener implements Runnable { tgwatch = 1; } + private void rlock() throws Exception { + database.getReadLock(); + info.getReadLock(); + } + + private void runlock() throws Exception { + database.releaseReadLock(); + info.releaseReadLock(); + } + + private void wlock() throws Exception { + database.getWriteLock(); + info.getWriteLock(); + } + + private void wunlock() throws Exception { + info.releaseWriteLock(); + database.releaseWriteLock(); + } + /** * Simply listen on TCP port, and thread connections * @@ -70,77 +90,121 @@ public class TCPlistener implements Runnable { public void run() { boolean g = false; boolean spin = true; - database.getReadLock(); - info.getReadLock(); - if(info.exists("OUTPORT")) { - tgwatch = 2; - } - try { - Socket server = new Socket(); - listener.setSoTimeout(50); // Half of the expected time from MUXlisten - info.releaseReadLock(); - database.releaseReadLock(); - while(spin) { - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); - try { - server = listener.accept(); - g = true; - } catch(SocketTimeoutException ste) { - g = false; - } - if(g) { - // toss the connection to a new thread. - TCPtoI2P conn_c = new TCPtoI2P(socketManager, server, info, database); - Thread t = new Thread(conn_c, "BOBTCPtoI2P"); - t.start(); - g = false; - } - } - //System.out.println("TCPlistener: destroySession"); - listener.close(); - } catch(IOException ioe) { - try { - listener.close(); - } catch(IOException e) { - } - // Fatal failure, cause a stop event - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); - if(spin) { - database.getWriteLock(); - info.getWriteLock(); - info.add("STOPPING", new Boolean(true)); - info.add("RUNNING", new Boolean(false)); - info.releaseWriteLock(); - database.releaseWriteLock(); - } - } +die: { + try { + rlock(); + } catch (Exception e) { + break die; + } + try { + if (info.exists("OUTPORT")) { + tgwatch = 2; + } + } catch (Exception e) { + try { + runlock(); + } catch (Exception e2) { + break die; + } + break die; + } + try { + runlock(); + } catch (Exception e) { + break die; + } + try { + Socket server = new Socket(); + listener.setSoTimeout(50); // Half of the expected time from MUXlisten + while (spin) { + try { + rlock(); + } catch (Exception e) { + break die; + } + try { + spin = info.get("RUNNING").equals(Boolean.TRUE); + } catch (Exception e) { + try { + runlock(); + } catch (Exception e2) { + break die; + } + break die; + } + try { + server = listener.accept(); + g = true; + } catch (SocketTimeoutException ste) { + g = false; + } + if (g) { + // toss the connection to a new thread. + TCPtoI2P conn_c = new TCPtoI2P(socketManager, server /* , info, database */); + Thread t = new Thread(conn_c, "BOBTCPtoI2P"); + t.start(); + g = false; + } + } + //System.out.println("TCPlistener: destroySession"); + listener.close(); + } catch (IOException ioe) { + try { + listener.close(); + } catch (IOException e) { + } + // Fatal failure, cause a stop event + try { + rlock(); + try { + spin = info.get("RUNNING").equals(Boolean.TRUE); + } catch (Exception e) { + runlock(); + break die; + } + } catch (Exception e) { + break die; + } + if (spin) { + try { + wlock(); + try { + info.add("STOPPING", new Boolean(true)); + info.add("RUNNING", new Boolean(false)); + } catch (Exception e) { + wunlock(); + break die; + } + } catch (Exception e) { + break die; + } + try { + wunlock(); + } catch (Exception e) { + break die; + } + } + } + } // need to kill off the socket manager too. I2PSession session = socketManager.getSession(); - if(session != null) { + if (session != null) { try { session.destroySession(); - } catch(I2PSessionException ex) { + } catch (I2PSessionException ex) { // nop } } //System.out.println("TCPlistener: Waiting for children"); - while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish + while (Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish try { Thread.sleep(100); //sleep for 100 ms (One tenth second) - } catch(Exception e) { + } catch (Exception e) { // nop - } + } } - //System.out.println("TCPlistener: Done."); + //System.out.println("TCPlistener: Done."); } } diff --git a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java index df61e78e1b..fe1ca32788 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java +++ b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java @@ -45,7 +45,7 @@ import net.i2p.i2ptunnel.I2PTunnel; public class TCPtoI2P implements Runnable { private I2PSocket I2P; - private NamedDB info, database; + // private NamedDB info, database; private Socket sock; private I2PSocketManager socketManager; @@ -84,13 +84,13 @@ public class TCPtoI2P implements Runnable { * Constructor * @param i2p * @param socket - * @param info - * @param database + * param info + * param database */ - TCPtoI2P(I2PSocketManager i2p, Socket socket, NamedDB info, NamedDB database) { + TCPtoI2P(I2PSocketManager i2p, Socket socket /*, NamedDB info, NamedDB database */) { this.sock = socket; - this.info = info; - this.database = database; + // this.info = info; + // this.database = database; this.socketManager = i2p; } @@ -110,6 +110,7 @@ public class TCPtoI2P implements Runnable { /** * TCP stream to I2P stream thread starter + * */ public void run() { String line, input; @@ -138,8 +139,8 @@ public class TCPtoI2P implements Runnable { InputStream Iin = I2P.getInputStream(); OutputStream Iout = I2P.getOutputStream(); // setup to cross the streams - TCPio conn_c = new TCPio(in, Iout, info, database); // app -> I2P - TCPio conn_a = new TCPio(Iin, out, info, database); // I2P -> app + TCPio conn_c = new TCPio(in, Iout /*, info, database */); // app -> I2P + TCPio conn_a = new TCPio(Iin, out /*, info, database */); // I2P -> app Thread t = new Thread(conn_c, "TCPioA"); Thread q = new Thread(conn_a, "TCPioB"); // Fire! @@ -167,7 +168,8 @@ public class TCPtoI2P implements Runnable { } catch(Exception e) { Emsg("ERROR " + e.toString(), out); } - } catch(IOException ioe) { + } catch(Exception e) { + // bail on anything else } try { // System.out.println("TCPtoI2P: Close I2P"); @@ -181,6 +183,5 @@ public class TCPtoI2P implements Runnable { } catch(Exception e) { } // System.out.println("TCPtoI2P: Done."); - } } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index d1e87948b1..eeea3faa97 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 7; + public final static long BUILD = 8; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 884663d077d2608ee6a0f854cfa4d81efa6c08dc Mon Sep 17 00:00:00 2001 From: sponge Date: Sat, 4 Apr 2009 19:48:27 +0000 Subject: [PATCH 080/112] Forgot the history.txt entry, oops!! --- history.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/history.txt b/history.txt index acc8a79b0f..1b09ebb63c 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,7 @@ +2009-04-04 sponge + * Hopeful fixups to the infamous orpahned tunnel problem. + * BOB now 0.0.5 + 2009-04-04 zzz * NTCP: Don't bid on messages too big to handle From 495558a949b47931516b9ab2cda542cc2aa22b71 Mon Sep 17 00:00:00 2001 From: mathiasdm Date: Mon, 6 Apr 2009 17:53:32 +0000 Subject: [PATCH 081/112] New application for I2P: desktopgui. Should eventually replace systray, and have more functionality. To use the application, you need to (by default) add the following to clients.config : # desktopgui clientApp.6.args= clientApp.6.delay=5 clientApp.6.main=desktopgui.Main clientApp.6.name=desktopgui clientApp.6.startOnLoad=true --- apps/desktopgui/LICENSE | 15 + apps/desktopgui/build.xml | 69 ++ .../desktopgui/resources/howto/howto.html | 261 ++++++++ .../desktopgui/resources/logo/logo.jpg | Bin 0 -> 1105 bytes apps/desktopgui/lib/appframework.jar | Bin 0 -> 264341 bytes apps/desktopgui/lib/swing-worker.jar | Bin 0 -> 11103 bytes apps/desktopgui/manifest.mf | 3 + apps/desktopgui/nbproject/build-impl.xml | 629 ++++++++++++++++++ apps/desktopgui/nbproject/genfiles.properties | 8 + apps/desktopgui/nbproject/project.properties | 68 ++ apps/desktopgui/nbproject/project.xml | 19 + .../org.jdesktop.application.Application | 1 + apps/desktopgui/src/desktopgui/Main.java | 109 +++ .../src/desktopgui/resources/Main.properties | 11 + apps/desktopgui/src/gui/SpeedSelector.form | 160 +++++ apps/desktopgui/src/gui/SpeedSelector.java | 176 +++++ apps/desktopgui/src/gui/SpeedSelector2.form | 116 ++++ apps/desktopgui/src/gui/SpeedSelector2.java | 174 +++++ apps/desktopgui/src/gui/SpeedSelector3.form | 223 +++++++ apps/desktopgui/src/gui/SpeedSelector3.java | 286 ++++++++ .../src/gui/SpeedSelectorConstants.java | 25 + apps/desktopgui/src/gui/Tray.java | 138 ++++ .../gui/resources/SpeedSelector.properties | 7 + .../gui/resources/SpeedSelector2.properties | 6 + .../gui/resources/SpeedSelector3.properties | 16 + .../src/persistence/PropertyManager.java | 72 ++ apps/desktopgui/src/router/RouterHandler.java | 38 ++ apps/desktopgui/src/router/RouterHelper.java | 13 + .../router/configuration/SpeedHandler.java | 34 + .../src/router/configuration/SpeedHelper.java | 28 + apps/desktopgui/src/util/IntegerVerifier.java | 32 + build.xml | 9 + installer/resources/wrapper.config | 4 + 33 files changed, 2750 insertions(+) create mode 100644 apps/desktopgui/LICENSE create mode 100644 apps/desktopgui/build.xml create mode 100644 apps/desktopgui/desktopgui/resources/howto/howto.html create mode 100644 apps/desktopgui/desktopgui/resources/logo/logo.jpg create mode 100644 apps/desktopgui/lib/appframework.jar create mode 100644 apps/desktopgui/lib/swing-worker.jar create mode 100644 apps/desktopgui/manifest.mf create mode 100644 apps/desktopgui/nbproject/build-impl.xml create mode 100644 apps/desktopgui/nbproject/genfiles.properties create mode 100644 apps/desktopgui/nbproject/project.properties create mode 100644 apps/desktopgui/nbproject/project.xml create mode 100644 apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application create mode 100644 apps/desktopgui/src/desktopgui/Main.java create mode 100644 apps/desktopgui/src/desktopgui/resources/Main.properties create mode 100644 apps/desktopgui/src/gui/SpeedSelector.form create mode 100644 apps/desktopgui/src/gui/SpeedSelector.java create mode 100644 apps/desktopgui/src/gui/SpeedSelector2.form create mode 100644 apps/desktopgui/src/gui/SpeedSelector2.java create mode 100644 apps/desktopgui/src/gui/SpeedSelector3.form create mode 100644 apps/desktopgui/src/gui/SpeedSelector3.java create mode 100644 apps/desktopgui/src/gui/SpeedSelectorConstants.java create mode 100644 apps/desktopgui/src/gui/Tray.java create mode 100644 apps/desktopgui/src/gui/resources/SpeedSelector.properties create mode 100644 apps/desktopgui/src/gui/resources/SpeedSelector2.properties create mode 100644 apps/desktopgui/src/gui/resources/SpeedSelector3.properties create mode 100644 apps/desktopgui/src/persistence/PropertyManager.java create mode 100644 apps/desktopgui/src/router/RouterHandler.java create mode 100644 apps/desktopgui/src/router/RouterHelper.java create mode 100644 apps/desktopgui/src/router/configuration/SpeedHandler.java create mode 100644 apps/desktopgui/src/router/configuration/SpeedHelper.java create mode 100644 apps/desktopgui/src/util/IntegerVerifier.java diff --git a/apps/desktopgui/LICENSE b/apps/desktopgui/LICENSE new file mode 100644 index 0000000000..61febe9011 --- /dev/null +++ b/apps/desktopgui/LICENSE @@ -0,0 +1,15 @@ +Desktop GUI: provides a simple GUI for I2P. +Copyright (C) 2009 Mathias De Maré + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; only version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. \ No newline at end of file diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml new file mode 100644 index 0000000000..c7ba1be112 --- /dev/null +++ b/apps/desktopgui/build.xml @@ -0,0 +1,69 @@ + + + + + + Builds, tests, and runs the project desktopgui. + + + diff --git a/apps/desktopgui/desktopgui/resources/howto/howto.html b/apps/desktopgui/desktopgui/resources/howto/howto.html new file mode 100644 index 0000000000..ea1b025c7b --- /dev/null +++ b/apps/desktopgui/desktopgui/resources/howto/howto.html @@ -0,0 +1,261 @@ + + + Small Guide to I2P + + +

    Small Guide to I2P

    + +

    So, what's this all about?

    + +

    I2P builds up a new net inside the usual internet, connecting nodes together + via encrypted connections. + It is a JAVA prgram with its most used part (the encryption of the data) written + in handoptimized assembler code. + It will use your bandwith, your RAM and your CPU. It will use them all up if you + do not limit it. + I2P will route unknown traffic through your node, even stuff you dislike. + As that data is encrypted, nobody knows whats data went to or drom your node. +

    + +

    + First, ALWAYS use the latest stable release. + Development releases are called "mtn version" and are marked with a -, e.g. + 0.6.5-1. Those are usually useable by all but could do harm to your I2P + experience. + You can get the latest MTN builds from my eepsite echelon.i2p, but always + remember: I built them, you need to trust me not to changed the code! + After you get the right "i2pupdate.zip" file, put that file into the I2P + directory and hit restart on the router console http://127.0.0.1:7657. + Do NOT deflate the zip file! +

    + +

    + I2P is very dynamic - after startup it tries to get known to other I2P routers + and measures their speed - you need to wait some 10-120 minutes until your + I2P router knows enough other ones to obtain full power of I2P. +

    + +

    Filesharing

    + +

    + I2P is able to do anonymous filesharing. + But as there are NO gateways between real net and I2P, you can only share/ + download torrents from within I2P. Look e.g. postman.i2p or planet.i2p. + You CANNOT use azureus, utorrent or any other usual client. + You cannot download anonymous torrents from mininova, piratebay or else. + You need to use I2P internal torrents and I2P aware programs like + I2Psnark (builtin, suitable for 1-20 torrents) + I2PRufus (external, python, high CPU load, suitable >20 torrents) http://echelon.i2p/i2prufus + I2P-BT (external, python) + I2PsnarkXL (mod of I2Psnark made by fwd) +

    + +

    + There are also gnutella and edonkey clients: + i2phex for gnutella (http://echelon.i2p/i2phex) + imule for edonkey (http://echelon.i2p/imule) +

    + +

    + Remember, as I2P uses other routers to route your traffic via 1-6 other PCs, + your transferrates in P2P are slower than in usual internet. + But you are anonymous, no one can easily (within 2 months-2 years) get your IP! + torrents inside of I2P reaches up to 50 kb/sec, usual are 10-20 kb/sec per torrent + i2phex reaches up to 20 kb/sec, usually 5-10 kb/sec + imule in times reaches 10 kb/sec, usually 5-10 kb/sec +

    + +

    + In I2PHex and imule you can just tell "share file or directory", in torrent + you need to create a .torrent file and upload that (and ONLY that small .torrent) + file to the trackers like tracker.postman.i2p/ +

    + +

    + I2P is a smaller net (1000 users) which grows slowly. As of which amount of shared + data will slowly rise. +

    + +

    + I2P is anonymous and it does not censor - there is no administrator. + There IS unwanted stuff like kiddyporn, nazism, or else. + If you dislike all this, do not use I2P. + There is NO way to prohibite this stuff to appear in a anonymous net like I2P. + (as that stuff is available shows the anonymity and transfer function of I2P + is working well enough) + You can delete the destinations in question from your local hosts.txt file (or + deface them) which will partly prevent you to reach those bad sies by accident. +

    + +

    Internet (the websites)

    + +

    + Only one outproxy (gateway I2P - webpages in usual Internet) is working. + It is NOT official from I2P, I2P does work without. + If that outproxy is slow, offline, gone,.. I2P still works on and cannot do + anything to change that failure. + That outproxy translates usual internet webpages into the I2P net and you can + reach them via your Router. + The best way for usual webpages is TOR, not that outproxy. + Remember: the owner of the outproxy got ALL traffic from all I2P users + visiting Internet pages and will risk that into the police! +

    + +

    + This proxy is false.i2p. In newer I2P routers it is enabled, but not in + older ones. Go to http://127.0.0.1:7657/i2ptunnel/index.jsp tunnels page and + click on the eepProxy tunnel. Change the entry for the "Outproxies" to false.i2p + and save. On the tunnels page, stop the epproxy tunnel and start it again, + now your router will use the false.i2p outproxy. +

    + +

    + No other (known) gateways are setup and running. No one we know will run + the gateway for torrent or any other P2P data (and risk his life). +

    + +

    Bandwidth

    + +

    http://127.0.0.1:7657/config.jsp

    + +

    Setup your bandwith wisely. Know your linespeed!

    + +

    + E.g. most common terms are: + 1Mbit = roughly 100 kbyte/sec + 10 MBit = roughly 1100 kbyte/sec + 512 kbit = roughly 50 kbyte/sec + or in germany: + 16000er = roughly 1500 kbyte/sec + 6000er = roughly 600 kbyte/sec + 1000er = roughly 100 kb/sec +

    + +

    + Set your bandwith limits to 10% under your line speed and burst rate to + your line speed. + Set the bandwith share percentage to: + >80% if lowest bandwith setting is >50k + >50% if lowest bandwith setting is >30k + >20% if lowest bandwith setting is >16k +

    + +

    There is no shared bandwith under 16k.

    + +

    + Limit your participating tunnels (shared bandwith) on: + http://127.0.0.1:7657/configadvanced.jsp + with the line: + router.maxParticipatingTunnels=500 +

    + +

    + 2000 is for roughly 600 kb/sec - very high value with high CPU load + 1000 is for roughly 300 kb/sec + 600 is a good value for 150-200kb/sec + 300 is roughly 90 kb/sec + 150 roughly 50 kb/sec + Remember: even failed tunnel requests will result in a part tunnel on the hops in between! + Those said, there are far more part tunnels unused than used in the live net under load, which + results in slower bandwith per tunnel in the end. + It is wise to first limit the bandwith and afterwards the part tunnels, e.g. set some more part + tunnels and let I2P reach the bandwith limit instead of the part tunnels limit! +

    + +

    What is shared bandwidth?

    + +

    + I2P transports your date from the client to the server through 1-6 hops + (other I2P routers). Each of this hops sees the data from you as "participating + tunnel" - which is the shared bandwith of them. + With this in mind, I2P needs some amount of this shared bandwith at some + amount of routers. + Share as much as you are able of - others will thank you! +

    + +

    + With the "share percentage" set like above, you will obtain enough speed for + your own traffic and obtain some participating tunnels (if >16kb/sec) with some + noise traffic to hide your traffic in the stream. +

    + +

    + With release 0.6.5 there is some method to prefer your own traffic ahead + of shared traffic which will result in better experience to you! +

    + +

    Addressbook

    + +

    + I2P uses a local addressbook to link short DNS names with the internal used 512bit + hashes (which are destination IDs). + Those links are saved insside the hosts.txt and userhosts.txt files in the i2p + directory. + Hosts which are not in those files cannot be reached via the short DNS names + and a error message with "jumper links" will appear. Those links will ask + some hosts services and forward to the correct site (if the site is known to them). + Those hosts services just made a form to add new "hosts" and those results public + available. + You can subscribe to those hosts service and let your hosts.txt file be updated + automatic. Go to http://127.0.0.1:7657/susidns/subscriptions.jsp SusiDNS + and enter the hosts services into the textbox (and save afterwards): + http://www.i2p2.i2p/hosts.txt + http://stats.i2p/cgi-bin/newhosts.txt + http://tino.i2p/hosts.txt + http://i2host.i2p/cgi-bin/i2hostag + You can add one of them, two or all. + SusiDNS will now ask those hosts for new entries to the hosts.txt and those + will be added to your hosts.txt. The userhosts.txt will ONLY be updated by + yourself (the user) and not be published into the net! + Remember, names once set could not be changed! If you loose your key (destination + ID) to your eepsite, service,..., there is no way to change the linking + between the DNS name and the (lost) destination ID automatic! Only manual by each + user itself - great topic to discuss of need to renew DNS hostnames. + As this subscription will not update old entries, you can "deface" unwanted + eepsites with a false key and if you hit the bad name in browser by accident, + you will not see the bad stuff! +

    + +

    Out of Memory errors

    + +

    + If your router hits the Out of Memory error - check your logs! + Usual point for OOM are to much torrents in i2psnark - i2psnark is a real + memory hogg and >10 torrents it requiers hell a lot of memory! +

    + +

    + Maybe it is possible for you to increase the wrapper memory config. + This ONLY works if you start the I2P service restartable with console + (on Windows). + In I2P directory edit the wrapper.config file and change the values: + wrapper.java.maxmemory=256 (or even to 512, IF possible) + Afterwards shutdown I2P complete (the service) and restart it. +

    + +

    Blocklists

    + +

    + Sometimes attackers trying to flood the I2P net and try to do some harm. + And some folks setting localnet IPs as their internet reachable address. + To prevent those bad router to harm the local router, I2P implemented + a local blocklist system. It is NOT integrated automatic as it could + really harm your I2P experience if setup the wrong way. + The way to enable blocklists is: + Get the file http://zzz.i2p/files/blocklist.txt and copy this file into the + I2P directory. + On http://127.0.0.1:7657/configadvanced.jsp set the option + router.blocklist.enable=true - click on Apply and restart the router + with the restart link left on router console. + The blockfile.txt file follows a special order, you´ll get it if you read it. + The first entry is the reason to be shown on http://127.0.0.1:7657/profiles.jsp + at the bottom in the shitlist section. + The second entry is the IP or the dest ID of a router. + Right now there are only private subnets in the blocklist AND one chinese router + which floods the floodfill DB while restarting every few minutes with a different + router ID and far to less bandwith for being a floodfill router. +

    + +

    (By echelon -- echelon.i2p )

    + + diff --git a/apps/desktopgui/desktopgui/resources/logo/logo.jpg b/apps/desktopgui/desktopgui/resources/logo/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1b5ccfc8728618281387d7a4b8ed11dedb8f39e GIT binary patch literal 1105 zcmex=>Qk2%s@GP0R~1ECMHHk7A|Ip0#;@gHg+ZsMotDHVG&VD zK`|-i@QBFC(`H=0b_1wEk`ZVavQZ4I42(?7EI=t`L12h5GP5u-!z^QD5@Z%qWMDB2 z6n0cnE}Z!P76T7Z5-2LjV9(%jJ@4S+OL6n{+*w zeZ`}!kPTbTpL6Z#pQn*i<5MhNG-;+uKp1~?`89K)X0ZE^o@$*VFd^iK9DY*3rIB~;yC zQulT2#uu~87uJ@(I9?{5Z6<&6N_Op1se9TY2XkhxHr}vQZ%N<|5r5Ik2VJGN8>*=s zH0lrb^=W7GnW$Iz#rX6*Q7f6froVH)_Z_}3d+ScYwD2@*PO0q+c70rvbRaVBrC)gB z;-cwk7k|vUD6;xNMXe7@HrGEnsems98OFoKGL^?&^S#olr(Bq(!qk{wo0+*#!CI|y*HVjfcE8rFu+OmG@IB`Id#$_4rM4DV ztu}q^zcekfY*wasL+z0Z8x~A*o0uxOB=*dfZ098Y&ZJuj$E4YE-22i=y_vyc(Qn4RrJhN^RI->RA|#S$y;yfo-e~zdGuOr z=aJ8A(@nm`=6&6LHZk?aBi);Cw7XXQcp$Ut6vK_n2A7%M{#X1&2e_IZ1y>_zyj!1jd-t24JN&<~F-PI<}n)Wp-$EiplWwG1Gv|YPZ zwsC&&;4DhI^H($VoUh#acA3ZL;@0u=bG+$!p7Z3xTcg@v-}s&De0|hUR`{sBI#lKJ zslIBW)#1B;cQ3uj$-^A_<+;;~A1Uhs)?9LxX}-3-Td_6On?r;%I4`hxqu~iidCyt?OF9SKI`Y} G|2F|xQl?`7 literal 0 HcmV?d00001 diff --git a/apps/desktopgui/lib/appframework.jar b/apps/desktopgui/lib/appframework.jar new file mode 100644 index 0000000000000000000000000000000000000000..0b8ff0145f010a8e1e9bfe065704cb4ad675f761 GIT binary patch literal 264341 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

    b3C#0adb5Et>9)K}}&1LqoU{fLLrP^m)I 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

    +&eO1NyNTy);90KwDiffI8) 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/nbproject/genfiles.properties b/apps/desktopgui/nbproject/genfiles.properties new file mode 100644 index 0000000000..1b326007c7 --- /dev/null +++ b/apps/desktopgui/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=c4b345cd +build.xml.script.CRC32=9785bb9a +build.xml.stylesheet.CRC32=be360661 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=c4b345cd +nbproject/build-impl.xml.script.CRC32=74d3fda2 +nbproject/build-impl.xml.stylesheet.CRC32=487672f9 diff --git a/apps/desktopgui/nbproject/project.properties b/apps/desktopgui/nbproject/project.properties new file mode 100644 index 0000000000..5e888e698b --- /dev/null +++ b/apps/desktopgui/nbproject/project.properties @@ -0,0 +1,68 @@ +application.desc=An anonymous communication network. +application.homepage=http://www.i2p2.de +application.title=I2P Desktop GUI +application.vendor=I2P Developers +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/desktopgui.jar +dist.javadoc.dir=${dist.dir}/javadoc +excludes= +file.reference.i2p.jar=../../core/java/build/i2p.jar +file.reference.router.jar=../../router/java/build/router.jar +includes=** +jar.compress=false +javac.classpath=\ + ${libs.swing-app-framework.classpath}:\ + ${file.reference.router.jar}:\ + ${file.reference.i2p.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.source=1.5 +javac.target=1.5 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit.classpath}:\ + ${libs.junit_4.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=desktopgui.Main +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value +# or test-sys-prop.name=value to set system properties for unit tests): +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/apps/desktopgui/nbproject/project.xml b/apps/desktopgui/nbproject/project.xml new file mode 100644 index 0000000000..09409a64cd --- /dev/null +++ b/apps/desktopgui/nbproject/project.xml @@ -0,0 +1,19 @@ + + + org.netbeans.modules.java.j2seproject + + + desktopgui + 1.6.5 + + + + + + + + + + + + diff --git a/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application b/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application new file mode 100644 index 0000000000..6cd2ac1fb1 --- /dev/null +++ b/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application @@ -0,0 +1 @@ +desktopgui.Main \ No newline at end of file diff --git a/apps/desktopgui/src/desktopgui/Main.java b/apps/desktopgui/src/desktopgui/Main.java new file mode 100644 index 0000000000..9d9708c8dd --- /dev/null +++ b/apps/desktopgui/src/desktopgui/Main.java @@ -0,0 +1,109 @@ +package desktopgui; + +/* + * Main.java + */ + + + +import gui.Tray; +import gui.SpeedSelector; +import java.awt.SystemTray; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import org.jdesktop.application.Application; +import org.jdesktop.application.SingleFrameApplication; +import persistence.PropertyManager; + +/** + * The main class of the application. + */ +public class Main extends SingleFrameApplication { + + /** + * At startup create and show the main frame of the application. + */ + @Override protected void startup() { + Properties props = PropertyManager.loadProps(); + + //First load: present screen with information (to help choose I2P settings) + if(props.getProperty(FIRSTLOAD).equals("true")) { + props.setProperty(FIRSTLOAD, "false"); + PropertyManager.saveProps(props); + new SpeedSelector(); //Start speed selector GUI + } + + if(SystemTray.isSupported()) { + tray = new Tray(); + } + else { //Alternative if SystemTray is not supported on the platform + } + } + + /** + * This method is to initialize the specified window by injecting resources. + * Windows shown in our application come fully initialized from the GUI + * builder, so this additional configuration is not needed. + */ + @Override protected void configureWindow(java.awt.Window root) { + } + + /** + * A convenient static getter for the application instance. + * @return the instance of Main + */ + public static Main getApplication() { + return Application.getInstance(Main.class); + } + + /** + * Main method launching the application. + */ + public static void main(String[] args) { + System.setProperty("java.awt.headless", "false"); //Make sure I2P is running in GUI mode for our application + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch (InstantiationException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch (IllegalAccessException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch (UnsupportedLookAndFeelException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + + Main main = getApplication(); + main.launchForeverLoop(); + main.startup(); + } + + /** + * Avoids the app terminating because no Window is opened anymore. + * More info: http://java.sun.com/javase/6/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown + */ + public void launchForeverLoop() { + Runnable r = new Runnable() { + public void run() { + try { + Object o = new Object(); + synchronized (o) { + o.wait(); + } + } catch (InterruptedException ie) { + } + } + }; + Thread t = new Thread(r); + t.setDaemon(false); + t.start(); + } + + private Tray tray = null; + ///Indicates if this is the first time the application loads + ///(is only true at the very start of loading the first time!) + private static final String FIRSTLOAD = "firstLoad"; +} diff --git a/apps/desktopgui/src/desktopgui/resources/Main.properties b/apps/desktopgui/src/desktopgui/resources/Main.properties new file mode 100644 index 0000000000..f79fe9a005 --- /dev/null +++ b/apps/desktopgui/src/desktopgui/resources/Main.properties @@ -0,0 +1,11 @@ +# Application global resources + +Application.name = desktopgui +Application.title = I2P Desktop GUI +Application.version = 0.7.1 +Application.vendor = I2P Developers +Application.homepage = http://www.i2p2.de +Application.description = An anonymous communication network. +Application.vendorId = I2P +Application.id = ${Application.name} +Application.lookAndFeel = system diff --git a/apps/desktopgui/src/gui/SpeedSelector.form b/apps/desktopgui/src/gui/SpeedSelector.form new file mode 100644 index 0000000000..b256265de0 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector.form @@ -0,0 +1,160 @@ + + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/src/gui/SpeedSelector.java b/apps/desktopgui/src/gui/SpeedSelector.java new file mode 100644 index 0000000000..19f4875341 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector.java @@ -0,0 +1,176 @@ +/* + * ProfileSelector.java + * + * Created on 3 april 2009, 13:57 + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Properties; +import javax.swing.JTextField; +import persistence.PropertyManager; +import util.IntegerVerifier; + +/** + * + * @author mathias + */ +public class SpeedSelector extends javax.swing.JFrame { + + /** Creates new form ProfileSelector */ + public SpeedSelector() { + this.props = PropertyManager.getProps(); + initComponents(); + initComponentsCustom(); + initSpeeds(props); + this.setVisible(true); + } + + public SpeedSelector(Point point, Dimension dimension) { + this(); + this.setLocation(point); + this.setSize(dimension); + } + + public void initComponentsCustom() { + ((JTextField)uploadChoice.getEditor().getEditorComponent()).setInputVerifier(new IntegerVerifier()); + ((JTextField)downloadChoice.getEditor().getEditorComponent()).setInputVerifier(new IntegerVerifier()); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + nextButton = new javax.swing.JButton(); + uploadLabel = new javax.swing.JLabel(); + downloadLabel = new javax.swing.JLabel(); + uploadChoice = new javax.swing.JComboBox(); + downloadChoice = new javax.swing.JComboBox(); + kbps1 = new javax.swing.JLabel(); + kbps2 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector.class); + setTitle(resourceMap.getString("Form.title")); // NOI18N + setName("Form"); // NOI18N + + nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N + nextButton.setName("nextButton"); // NOI18N + nextButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + nextButtonMouseClicked(evt); + } + }); + + uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N + uploadLabel.setName("uploadLabel"); // NOI18N + + downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N + downloadLabel.setName("downloadLabel"); // NOI18N + + uploadChoice.setEditable(true); + uploadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" })); + uploadChoice.setSelectedIndex(3); + uploadChoice.setName("uploadChoice"); // NOI18N + + downloadChoice.setEditable(true); + downloadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" })); + downloadChoice.setSelectedIndex(3); + downloadChoice.setName("downloadChoice"); // NOI18N + + kbps1.setText(resourceMap.getString("kbps1.text")); // NOI18N + kbps1.setName("kbps1"); // NOI18N + + kbps2.setText(resourceMap.getString("kbps2.text")); // NOI18N + kbps2.setName("kbps2"); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(49, 49, 49) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(downloadLabel) + .addComponent(uploadLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(downloadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(kbps2)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(uploadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(kbps1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(nextButton) + .addGap(34, 34, 34)))) + .addGap(40, 40, 40)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(67, 67, 67) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadLabel) + .addComponent(uploadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(kbps1)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(downloadLabel) + .addComponent(downloadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(kbps2)) + .addContainerGap(173, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(271, Short.MAX_VALUE) + .addComponent(nextButton) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + +private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked + props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, uploadChoice.getSelectedItem().toString()); + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, downloadChoice.getSelectedItem().toString()); + PropertyManager.saveProps(props); + new SpeedSelector2(this.getLocationOnScreen(), this.getSize()); + this.dispose(); +}//GEN-LAST:event_nextButtonMouseClicked + +private void initSpeeds(Properties props) { + String up = props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE); + String down = props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE); + + if(up == null) + props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, "1000"); + if(down == null) + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, "1000"); + + uploadChoice.setSelectedItem(props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE)); + downloadChoice.setSelectedItem(props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE)); +} + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox downloadChoice; + private javax.swing.JLabel downloadLabel; + private javax.swing.JLabel kbps1; + private javax.swing.JLabel kbps2; + private javax.swing.JButton nextButton; + private javax.swing.JComboBox uploadChoice; + private javax.swing.JLabel uploadLabel; + // End of variables declaration//GEN-END:variables + + Properties props; +} diff --git a/apps/desktopgui/src/gui/SpeedSelector2.form b/apps/desktopgui/src/gui/SpeedSelector2.form new file mode 100644 index 0000000000..2f0abc7863 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector2.form @@ -0,0 +1,116 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/apps/desktopgui/src/gui/SpeedSelector2.java b/apps/desktopgui/src/gui/SpeedSelector2.java new file mode 100644 index 0000000000..a02ef9b53d --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector2.java @@ -0,0 +1,174 @@ +/* + * ProfileSelector2.java + * + * Created on 3 april 2009, 14:36 + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Enumeration; +import java.util.Properties; +import javax.swing.AbstractButton; +import persistence.PropertyManager; + +/** + * + * @author mathias + */ +public class SpeedSelector2 extends javax.swing.JFrame { + Properties props; + + /** Creates new form ProfileSelector2 */ + public SpeedSelector2(Point point, Dimension dimension) { + this.props = PropertyManager.getProps(); + initComponents(); + this.setLocation(point); + this.setSize(dimension); + loadButtonSelection(); + this.setVisible(true); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + nextButton = new javax.swing.JButton(); + returnButton = new javax.swing.JButton(); + questionLabel = new javax.swing.JLabel(); + browseButton = new javax.swing.JRadioButton(); + downloadButton = new javax.swing.JRadioButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector2.class); + setTitle(resourceMap.getString("Form.title")); // NOI18N + setName("Form"); // NOI18N + + nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N + nextButton.setName("nextButton"); // NOI18N + nextButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + nextButtonMouseClicked(evt); + } + }); + + returnButton.setText(resourceMap.getString("returnButton.text")); // NOI18N + returnButton.setName("returnButton"); // NOI18N + returnButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + returnButtonMouseClicked(evt); + } + }); + + questionLabel.setText(resourceMap.getString("questionLabel.text")); // NOI18N + questionLabel.setName("questionLabel"); // NOI18N + + buttonGroup1.add(browseButton); + browseButton.setText(resourceMap.getString("browseButton.text")); // NOI18N + browseButton.setName("browseButton"); // NOI18N + + buttonGroup1.add(downloadButton); + downloadButton.setText(resourceMap.getString("downloadButton.text")); // NOI18N + downloadButton.setName("downloadButton"); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(406, Short.MAX_VALUE) + .addComponent(returnButton) + .addGap(18, 18, 18) + .addComponent(nextButton) + .addGap(74, 74, 74)) + .addGroup(layout.createSequentialGroup() + .addGap(42, 42, 42) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(questionLabel) + .addGroup(layout.createSequentialGroup() + .addGap(12, 12, 12) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(downloadButton) + .addComponent(browseButton)))) + .addContainerGap(32, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(54, 54, 54) + .addComponent(questionLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(browseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(downloadButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 120, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nextButton) + .addComponent(returnButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + +private void returnButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_returnButtonMouseClicked + saveButtonSelection(); + PropertyManager.saveProps(props); + new SpeedSelector(this.getLocationOnScreen(), this.getSize()).setVisible(true); + this.dispose(); +}//GEN-LAST:event_returnButtonMouseClicked + +private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked + saveButtonSelection(); + PropertyManager.saveProps(props); + new SpeedSelector3(this.getLocationOnScreen(), this.getSize()).setVisible(true); + this.dispose(); +}//GEN-LAST:event_nextButtonMouseClicked + +private void loadButtonSelection() { + Enumeration elements = buttonGroup1.getElements(); + while(elements.hasMoreElements()) { + AbstractButton button = elements.nextElement(); + if(button == null) + continue; + if(props.getProperty(SpeedSelectorConstants.USERTYPE) == null) + break; + String type = button.getText().split(":")[0]; + if(type.equals(props.getProperty(SpeedSelectorConstants.USERTYPE))) { + button.setSelected(true); + break; + } + } +} + +private void saveButtonSelection() { + Enumeration elements = buttonGroup1.getElements(); + while(elements.hasMoreElements()) { + AbstractButton button = elements.nextElement(); + if(button == null) + continue; + if(button.isSelected()) { + String type = button.getText().split(":")[0]; + props.setProperty(SpeedSelectorConstants.USERTYPE, type); + break; + } + } +} + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton browseButton; + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.JRadioButton downloadButton; + private javax.swing.JButton nextButton; + private javax.swing.JLabel questionLabel; + private javax.swing.JButton returnButton; + // End of variables declaration//GEN-END:variables + +} diff --git a/apps/desktopgui/src/gui/SpeedSelector3.form b/apps/desktopgui/src/gui/SpeedSelector3.form new file mode 100644 index 0000000000..419a22fc15 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector3.form @@ -0,0 +1,223 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/apps/desktopgui/src/gui/SpeedSelector3.java b/apps/desktopgui/src/gui/SpeedSelector3.java new file mode 100644 index 0000000000..e226f74226 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector3.java @@ -0,0 +1,286 @@ +/* + * ProfileSelector3.java + * + * Created on 3 april 2009, 15:17 + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Properties; +import persistence.PropertyManager; +import router.configuration.SpeedHandler; +import router.configuration.SpeedHelper; + +/** + * + * @author mathias + */ +public class SpeedSelector3 extends javax.swing.JFrame { + Properties props; + + /** Creates new form ProfileSelector3 */ + public SpeedSelector3(Point point, Dimension dimension) { + this.props = PropertyManager.getProps(); + initComponents(); + this.setLocation(point); + this.setSize(dimension); + initSpeeds(); + this.setVisible(true); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + finishButton = new javax.swing.JButton(); + previousButton = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + uploadLabel = new javax.swing.JLabel(); + downloadLabel = new javax.swing.JLabel(); + uploadBurstLabel = new javax.swing.JLabel(); + downloadBurstLabel = new javax.swing.JLabel(); + uploadUsageLabel = new javax.swing.JLabel(); + downloadUsageLabel = new javax.swing.JLabel(); + uploadField = new javax.swing.JTextField(); + uploadBurstField = new javax.swing.JTextField(); + downloadField = new javax.swing.JTextField(); + downloadBurstField = new javax.swing.JTextField(); + uploadMonth = new javax.swing.JLabel(); + downloadMonth = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector3.class); + setTitle(resourceMap.getString("Form.title")); // NOI18N + setName("Form"); // NOI18N + + finishButton.setText(resourceMap.getString("finishButton.text")); // NOI18N + finishButton.setName("finishButton"); // NOI18N + finishButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + finishButtonMouseClicked(evt); + } + }); + + previousButton.setText(resourceMap.getString("previousButton.text")); // NOI18N + previousButton.setName("previousButton"); // NOI18N + previousButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + previousButtonMouseClicked(evt); + } + }); + + jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N + jLabel1.setName("jLabel1"); // NOI18N + + uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N + uploadLabel.setName("uploadLabel"); // NOI18N + + downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N + downloadLabel.setName("downloadLabel"); // NOI18N + + uploadBurstLabel.setText(resourceMap.getString("uploadBurstLabel.text")); // NOI18N + uploadBurstLabel.setName("uploadBurstLabel"); // NOI18N + + downloadBurstLabel.setText(resourceMap.getString("downloadBurstLabel.text")); // NOI18N + downloadBurstLabel.setName("downloadBurstLabel"); // NOI18N + + uploadUsageLabel.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N + uploadUsageLabel.setName("uploadUsageLabel"); // NOI18N + + downloadUsageLabel.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N + downloadUsageLabel.setName("downloadUsageLabel"); // NOI18N + + uploadField.setText(resourceMap.getString("uploadField.text")); // NOI18N + uploadField.setMinimumSize(new java.awt.Dimension(77, 27)); + uploadField.setName("uploadField"); // NOI18N + uploadField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + speedFieldKeyReleased(evt); + } + }); + + uploadBurstField.setText(resourceMap.getString("uploadBurstField.text")); // NOI18N + uploadBurstField.setMinimumSize(new java.awt.Dimension(77, 27)); + uploadBurstField.setName("uploadBurstField"); // NOI18N + + downloadField.setText(resourceMap.getString("downloadField.text")); // NOI18N + downloadField.setMinimumSize(new java.awt.Dimension(77, 27)); + downloadField.setName("downloadField"); // NOI18N + downloadField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + speedFieldKeyReleased(evt); + } + }); + + downloadBurstField.setText(resourceMap.getString("downloadBurstField.text")); // NOI18N + downloadBurstField.setMinimumSize(new java.awt.Dimension(77, 27)); + downloadBurstField.setName("downloadBurstField"); // NOI18N + + uploadMonth.setText(resourceMap.getString("uploadMonth.text")); // NOI18N + uploadMonth.setName("uploadMonth"); // NOI18N + + downloadMonth.setText(resourceMap.getString("downloadMonth.text")); // NOI18N + downloadMonth.setName("downloadMonth"); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel1) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(uploadLabel) + .addComponent(uploadBurstLabel) + .addComponent(uploadUsageLabel)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(uploadBurstField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(uploadField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(uploadMonth, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(46, 46, 46) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(downloadLabel) + .addComponent(downloadBurstLabel) + .addComponent(downloadUsageLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(downloadField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(downloadMonth, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(downloadBurstField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addGap(18, 18, 18)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(previousButton) + .addGap(18, 18, 18) + .addComponent(finishButton) + .addGap(33, 33, 33))) + .addGap(400, 400, 400)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(81, 81, 81) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadLabel) + .addComponent(downloadLabel) + .addComponent(uploadField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(downloadField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadBurstLabel) + .addComponent(downloadBurstLabel) + .addComponent(downloadBurstField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(uploadBurstField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadUsageLabel) + .addComponent(downloadUsageLabel) + .addComponent(uploadMonth) + .addComponent(downloadMonth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(previousButton) + .addComponent(finishButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + +private void previousButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_previousButtonMouseClicked + saveSpeeds(); + PropertyManager.saveProps(props); + new SpeedSelector2(this.getLocationOnScreen(), this.getSize()).setVisible(true); + this.dispose(); +}//GEN-LAST:event_previousButtonMouseClicked + +private void finishButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_finishButtonMouseClicked + saveSpeeds(); + PropertyManager.saveProps(props); + + int maxDownload = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXDOWNLOAD)); + int maxUpload = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXUPLOAD)); + int maxUploadBurst = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXUPLOADBURST)); + int maxDownloadBurst = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXDOWNLOADBURST)); + + //Working in kB, not kb! + SpeedHandler.setInboundBandwidth(maxDownload/8); + SpeedHandler.setOutboundBandwidth(maxUpload/8); + SpeedHandler.setInboundBurstBandwidth(maxDownloadBurst); + SpeedHandler.setOutboundBurstBandwidth(maxUploadBurst/8); + + this.dispose(); +}//GEN-LAST:event_finishButtonMouseClicked + +private void speedFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_speedFieldKeyReleased + try { + initUsage(uploadField.getText(), downloadField.getText()); + } + catch(NumberFormatException e) { + return; + } +}//GEN-LAST:event_speedFieldKeyReleased + + protected void initSpeeds() { + String up = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String upBurst = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String down = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String downBurst = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String userType = props.getProperty(SpeedSelectorConstants.USERTYPE); + + uploadField.setText(up); + uploadBurstField.setText(upBurst); + downloadField.setText(down); + downloadBurstField.setText(downBurst); + + initUsage(up, down); + } + + protected void saveSpeeds() { + props.setProperty(SpeedSelectorConstants.MAXUPLOAD, uploadField.getText()); + props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, uploadBurstField.getText()); + props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, downloadField.getText()); + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, downloadBurstField.getText()); + } + + protected void initUsage(String upload, String download) { + uploadMonth.setText(SpeedHelper.calculateMonthlyUsage(Integer.parseInt(upload)/8) + " GB"); + downloadMonth.setText(SpeedHelper.calculateMonthlyUsage(Integer.parseInt(download)/8) + " GB"); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTextField downloadBurstField; + private javax.swing.JLabel downloadBurstLabel; + private javax.swing.JTextField downloadField; + private javax.swing.JLabel downloadLabel; + private javax.swing.JLabel downloadMonth; + private javax.swing.JLabel downloadUsageLabel; + private javax.swing.JButton finishButton; + private javax.swing.JLabel jLabel1; + private javax.swing.JButton previousButton; + private javax.swing.JTextField uploadBurstField; + private javax.swing.JLabel uploadBurstLabel; + private javax.swing.JTextField uploadField; + private javax.swing.JLabel uploadLabel; + private javax.swing.JLabel uploadMonth; + private javax.swing.JLabel uploadUsageLabel; + // End of variables declaration//GEN-END:variables + +} diff --git a/apps/desktopgui/src/gui/SpeedSelectorConstants.java b/apps/desktopgui/src/gui/SpeedSelectorConstants.java new file mode 100644 index 0000000000..ea5e32427b --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelectorConstants.java @@ -0,0 +1,25 @@ +package gui; + +/** + * + * @author mathias + */ +public class SpeedSelectorConstants { + ///Maximum upload speed for the internet connection + public static final String MAXUPLOADCAPABLE = "maxUploadCapable"; + ///Maximum download speed for the internet connection + public static final String MAXDOWNLOADCAPABLE = "maxDownloadCapable"; + + //User profile type: what behaviour does this user have while using IP2? + public static final String USERTYPE = "userType"; + + //Maximum upload speed for I2P + public static final String MAXUPLOAD = "maxUpload"; + //Maximum upload burst speed for I2P + public static final String MAXUPLOADBURST = "maxUploadBurst"; + + //Maximum download speed for I2P + public static final String MAXDOWNLOAD = "maxDownload"; + //Maximum download burst speed for I2P + public static final String MAXDOWNLOADBURST = "maxDownloadBurst"; +} diff --git a/apps/desktopgui/src/gui/Tray.java b/apps/desktopgui/src/gui/Tray.java new file mode 100644 index 0000000000..6ed5ea0d49 --- /dev/null +++ b/apps/desktopgui/src/gui/Tray.java @@ -0,0 +1,138 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package gui; + +import desktopgui.*; +import java.awt.AWTException; +import java.awt.Desktop; +import java.awt.Image; +import java.awt.MenuItem; +import java.awt.Menu; +import java.awt.PopupMenu; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.awt.TrayIcon; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Level; +import java.util.logging.Logger; +import router.RouterHandler; + +/** + * + * @author mathias + */ +public class Tray { + + public Tray() { + tray = SystemTray.getSystemTray(); + loadSystemTray(); + } + + private void loadSystemTray() { + + Image image = Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo.jpg"); + + PopupMenu popup = new PopupMenu(); + + //Create menu items to put in the popup menu + MenuItem browserLauncher = new MenuItem("Launch browser"); + browserLauncher.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + if(Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI("http://localhost:7657")); + } catch (URISyntaxException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + }); + MenuItem howto = new MenuItem("How to use I2P"); + howto.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + if(Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + File f = new File("desktopgui/resources/howto/howto.html"); + desktop.browse(new URI("file://" + f.getAbsolutePath())); + } catch (URISyntaxException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + }); + Menu config = new Menu("Configuration"); + MenuItem speedConfig = new MenuItem("Speed"); + speedConfig.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + (new SpeedSelector()).setVisible(true); + } + + }); + MenuItem advancedConfig = new MenuItem("Advanced Configuration"); + advancedConfig.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + if(Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI("http://localhost:7657/config.jsp")); + } catch (URISyntaxException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + }); + MenuItem shutdown = new MenuItem("Shutdown I2P"); + shutdown.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + RouterHandler.setStatus(RouterHandler.SHUTDOWN_GRACEFULLY); + } + + }); + + //Add menu items to popup menu + popup.add(browserLauncher); + popup.add(howto); + + config.add(speedConfig); + config.add(advancedConfig); + popup.add(config); + + popup.add(shutdown); + + //Add tray icon + trayIcon = new TrayIcon(image, "I2P: the anonymous network", popup); + try { + tray.add(trayIcon); + } catch (AWTException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private SystemTray tray = null; + private TrayIcon trayIcon = null; + +} diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector.properties b/apps/desktopgui/src/gui/resources/SpeedSelector.properties new file mode 100644 index 0000000000..1a476d8741 --- /dev/null +++ b/apps/desktopgui/src/gui/resources/SpeedSelector.properties @@ -0,0 +1,7 @@ + +Form.title=I2P Configuration +nextButton.text=Next +uploadLabel.text=What is your maximum upload speed? +downloadLabel.text=What is your maximum download speed? +kbps1.text=kbit/second +kbps2.text=kbit/second diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties new file mode 100644 index 0000000000..704d9909ec --- /dev/null +++ b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties @@ -0,0 +1,6 @@ +returnButton.text=Previous +Form.title=I2P Configuration +questionLabel.text=Which of these descriptions fits you best? +browseButton.text=Browsing: I want to use I2P to browse websites anonymously, no heavy usage. +downloadButton.text=Downloading: I want to use I2P for downloads and filesharing, heavy usage. +nextButton.text=Next diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties new file mode 100644 index 0000000000..49ee18ba00 --- /dev/null +++ b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties @@ -0,0 +1,16 @@ +Form.title=I2P Configuration +jLabel1.text=The profile information your entered, indicates that these are your optimal settings: +previousButton.text=Previous +finishButton.text=Finish +uploadLabel.text=Upload Speed: +uploadBurstLabel.text=Burst Upload Speed: +downloadLabel.text=Download Speed: +downloadBurstLabel.text=Burst Download Speed: +uploadUsageLabel.text=Monthy upload usage: +downloadUsageLabel.text=Monthy Download Usage: +uploadField.text=jTextField1 +uploadBurstField.text=jTextField2 +uploadMonth.text=jLabel8 +downloadMonth.text=jLabel9 +downloadField.text=jTextField4 +downloadBurstField.text=jTextField5 diff --git a/apps/desktopgui/src/persistence/PropertyManager.java b/apps/desktopgui/src/persistence/PropertyManager.java new file mode 100644 index 0000000000..bacbf348ac --- /dev/null +++ b/apps/desktopgui/src/persistence/PropertyManager.java @@ -0,0 +1,72 @@ +package persistence; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author mathias + */ +public class PropertyManager { + + public static void setProps(Properties props) { + PropertyManager.props = props; + } + + public static Properties getProps() { + return props; + } + + public static Properties loadProps() { + Properties defaultProps = new Properties(); + defaultProps.setProperty("firstLoad", "true"); + + // create application properties with default + Properties applicationProps = new Properties(defaultProps); + + // now load properties from last invocation + FileInputStream in; + try { + in = new FileInputStream(PROPSLOCATION); + applicationProps.load(in); + in.close(); + } catch (FileNotFoundException ex) { + //Nothing serious, just means it's being loaded for the first time. + } catch(IOException ex) { + Logger.getLogger(PropertyManager.class.getName()).log(Level.INFO, null, ex); + } + props = applicationProps; + return applicationProps; + } + + public static void saveProps(Properties props) { + FileOutputStream out; + try { + File d = new File(PROPSDIRECTORY); + if(!d.exists()) + d.mkdir(); + File f = new File(PROPSLOCATION); + if(!f.exists()) + f.createNewFile(); + out = new FileOutputStream(f); + props.store(out, PROPSLOCATION); + } catch (FileNotFoundException ex) { + Logger.getLogger(PropertyManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(PropertyManager.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private static Properties props; + + ///Location where we store the Application Properties + public static final String PROPSDIRECTORY = "desktopgui"; + public static final String PROPSFILENAME = "appProperties"; + public static final String PROPSLOCATION = PROPSDIRECTORY + File.separator + PROPSFILENAME; +} diff --git a/apps/desktopgui/src/router/RouterHandler.java b/apps/desktopgui/src/router/RouterHandler.java new file mode 100644 index 0000000000..0752f877b5 --- /dev/null +++ b/apps/desktopgui/src/router/RouterHandler.java @@ -0,0 +1,38 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package router; + +import java.util.logging.Level; +import java.util.logging.Logger; +import net.i2p.router.RouterContext; + +/** + * + * @author mathias + */ +public class RouterHandler { + public static final int SHUTDOWN_GRACEFULLY = 0; + public static void setStatus(int status) { + if(status == SHUTDOWN_GRACEFULLY) { + Thread t = new Thread(new Runnable() { + + public void run() { + RouterContext context = RouterHelper.getContext(); + context.router().shutdownGracefully(); + while(context.router().getShutdownTimeRemaining()>0) + try { + Thread.sleep(context.router().getShutdownTimeRemaining()); + } catch (InterruptedException ex) { + Logger.getLogger(RouterHandler.class.getName()).log(Level.SEVERE, null, ex); + } + System.exit(0); + } + + }); + t.start(); + } + } +} diff --git a/apps/desktopgui/src/router/RouterHelper.java b/apps/desktopgui/src/router/RouterHelper.java new file mode 100644 index 0000000000..c5fc78e959 --- /dev/null +++ b/apps/desktopgui/src/router/RouterHelper.java @@ -0,0 +1,13 @@ +package router; + +import net.i2p.router.RouterContext; + +/** + * + * @author mathias + */ +public class RouterHelper { + public static RouterContext getContext() { + return (RouterContext) RouterContext.listContexts().get(0); + } +} diff --git a/apps/desktopgui/src/router/configuration/SpeedHandler.java b/apps/desktopgui/src/router/configuration/SpeedHandler.java new file mode 100644 index 0000000000..235790792a --- /dev/null +++ b/apps/desktopgui/src/router/configuration/SpeedHandler.java @@ -0,0 +1,34 @@ +package router.configuration; + +import net.i2p.router.RouterContext; +import net.i2p.router.transport.FIFOBandwidthRefiller; +import router.RouterHelper; + +/** + * + * @author mathias + */ +public class SpeedHandler { + + public static void setInboundBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + public static void setOutboundBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + public static void setInboundBurstBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + public static void setOutboundBurstBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + private static final RouterContext context = RouterHelper.getContext(); +} diff --git a/apps/desktopgui/src/router/configuration/SpeedHelper.java b/apps/desktopgui/src/router/configuration/SpeedHelper.java new file mode 100644 index 0000000000..1cce2f9c03 --- /dev/null +++ b/apps/desktopgui/src/router/configuration/SpeedHelper.java @@ -0,0 +1,28 @@ +package router.configuration; + +/** + * + * @author mathias + */ +public class SpeedHelper { + public static final String USERTYPE_BROWSING = "Browsing"; + public static final String USERTYPE_DOWNLOADING = "Downloading"; + + public static int calculateSpeed(String capable, String profile) { + int capableSpeed = Integer.parseInt(capable); + int advisedSpeed = capableSpeed; + if(capableSpeed > 1000) { + if(profile.equals(USERTYPE_BROWSING)) //Don't overdo usage for people just wanting to browse (we don't want to drive them away due to resource hogging) + advisedSpeed *= 0.6; + else if(profile.equals(USERTYPE_DOWNLOADING)) + advisedSpeed *= 0.8; + } + else + advisedSpeed *= 0.6; //Lower available bandwidth: don't hog all the bandwidth + return advisedSpeed; + } + + public static int calculateMonthlyUsage(int kbytes) { + return (kbytes*3600*24*31)/1000000; + } +} diff --git a/apps/desktopgui/src/util/IntegerVerifier.java b/apps/desktopgui/src/util/IntegerVerifier.java new file mode 100644 index 0000000000..74f87961da --- /dev/null +++ b/apps/desktopgui/src/util/IntegerVerifier.java @@ -0,0 +1,32 @@ +package util; + +import javax.swing.InputVerifier; +import javax.swing.JComponent; +import javax.swing.JTextField; + +/** + * + * @author mathias + */ + +public class IntegerVerifier extends InputVerifier { + + @Override + public boolean verify(JComponent arg0) { + JTextField jtf = (JTextField) arg0; + return verify(jtf.getText()); + } + + @Override + public boolean shouldYieldFocus(JComponent input) { + return verify(input); + } + + public static boolean verify(String s) { + for(int i=0;i '9' || s.charAt(i) < '0') + return false; + return true; + } + +} \ No newline at end of file diff --git a/build.xml b/build.xml index ef84b2aab3..26254cd15a 100644 --- a/build.xml +++ b/build.xml @@ -37,6 +37,7 @@ + @@ -104,6 +105,7 @@ + @@ -214,6 +216,13 @@ + + + + + + + diff --git a/installer/resources/wrapper.config b/installer/resources/wrapper.config index 4d09ba12c2..42958ba779 100644 --- a/installer/resources/wrapper.config +++ b/installer/resources/wrapper.config @@ -54,6 +54,10 @@ wrapper.java.classpath.17=lib/systray.jar wrapper.java.classpath.18=lib/systray4j.jar # BOB wrapper.java.classpath.19=lib/BOB.jar +# desktopgui +wrapper.java.classpath.20=lib/appframework.jar +wrapper.java.classpath.21=lib/swing-worker.jar +wrapper.java.classpath.22=lib/desktopgui.jar # Java Library Path (location of Wrapper.DLL or libwrapper.so) wrapper.java.library.path.1=. From 9e7dd238a44140ae388637d6ce025ed4893a2636 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 6 Apr 2009 19:43:54 +0000 Subject: [PATCH 082/112] prevent NPE --- .../net/i2p/router/transport/ntcp/EstablishState.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index 942d6cbc99..f8ccf0814d 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -462,7 +462,6 @@ public class EstablishState { if (!_verified) { _context.statManager().addRateData("ntcp.invalidSignature", 1, 0); fail("Signature was invalid - attempt to spoof " + _con.getRemotePeer().calculateHash().toBase64() + "?"); - return; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "signature verified from Bob. done!"); @@ -472,10 +471,12 @@ public class EstablishState { byte nextReadIV[] = new byte[16]; System.arraycopy(_e_bobSig, _e_bobSig.length-16, nextReadIV, 0, nextReadIV.length); _con.finishOutboundEstablishment(_dh.getSessionKey(), (_tsA-_tsB), nextWriteIV, nextReadIV); // skew in seconds - _transport.setIP(_con.getRemotePeer().calculateHash(), - _con.getChannel().socket().getInetAddress().getAddress()); - return; + // if socket gets closed this will be null - prevent NPE + InetAddress ia = _con.getChannel().socket().getInetAddress(); + if (ia != null) + _transport.setIP(_con.getRemotePeer().calculateHash(), ia.getAddress()); } + return; } } } From f5614c8a416ed6749fe212ca4a6fee7cfc0f84f7 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 6 Apr 2009 19:44:47 +0000 Subject: [PATCH 083/112] synchronize datagram maker in I2PSinks --- .../i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java | 8 +++++--- .../java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java index 6a32801b7b..f7a1bf541e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java @@ -37,9 +37,11 @@ public class I2PSink implements Sink { //System.out.print("w"); // create payload byte[] payload; - if(!this.raw) - payload = this.maker.makeI2PDatagram(data); - else + if(!this.raw) { + synchronized(this.maker) { + payload = this.maker.makeI2PDatagram(data); + } + } else payload = data; // send message diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java index 8707d97794..54d5a7d97d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java @@ -35,9 +35,11 @@ public class I2PSinkAnywhere implements Sink { public synchronized void send(Destination to, byte[] data) { // create payload byte[] payload; - if(!this.raw) - payload = this.maker.makeI2PDatagram(data); - else + if(!this.raw) { + synchronized(this.maker) { + payload = this.maker.makeI2PDatagram(data); + } + } else payload = data; // send message From 37667247c3d2c0b7a5d26bb6d136497cddd05294 Mon Sep 17 00:00:00 2001 From: sponge Date: Mon, 6 Apr 2009 22:40:22 +0000 Subject: [PATCH 084/112] 2009-04-06 sponge SimpleScheduler SimpleTimer2 debugging added. Fix build files for desktopgui. --- apps/desktopgui/build.xml | 5 ++++ apps/desktopgui/nbproject/build-impl.xml | 25 ++++++++++++++----- apps/desktopgui/nbproject/genfiles.properties | 8 +++--- apps/desktopgui/nbproject/project.properties | 4 +-- .../src/net/i2p/util/SimpleScheduler.java | 7 +++++- core/java/src/net/i2p/util/SimpleTimer2.java | 7 +++++- history.txt | 4 +++ .../src/net/i2p/router/RouterVersion.java | 2 +- 8 files changed, 47 insertions(+), 15 deletions(-) diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml index c7ba1be112..dbcbab67c9 100644 --- a/apps/desktopgui/build.xml +++ b/apps/desktopgui/build.xml @@ -2,6 +2,11 @@ + + + + + Builds, tests, and runs the project desktopgui. diff --git a/apps/desktopgui/nbproject/build-impl.xml b/apps/desktopgui/nbproject/build-impl.xml index f8fea458d1..039f8788f1 100644 --- a/apps/desktopgui/nbproject/build-impl.xml +++ b/apps/desktopgui/nbproject/build-impl.xml @@ -152,7 +152,7 @@ is divided into following sections: - + @@ -218,13 +218,13 @@ is divided into following sections: - + - + @@ -255,6 +255,12 @@ is divided into following sections: + + + + + + @@ -264,7 +270,7 @@ is divided into following sections: - + @@ -311,6 +317,13 @@ is divided into following sections: =================== --> + + + + + + + @@ -331,7 +344,7 @@ is divided into following sections: - + @@ -345,7 +358,7 @@ is divided into following sections: - + From 91de396821334b9c88fc5cfbd1b4f76df778ddad Mon Sep 17 00:00:00 2001 From: amiga4000 Date: Tue, 7 Apr 2009 12:05:00 +0000 Subject: [PATCH 089/112] added echelon.i2p to readme of I2P router --- readme.html | 1 + readme_de.html | 3 ++- readme_fr.html | 1 + readme_sv.html | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/readme.html b/readme.html index 0e4891b5d8..c359913350 100644 --- a/readme.html +++ b/readme.html @@ -16,6 +16,7 @@ you can:

  • ugha.i2p: ugha's eepsite, a wiki that anyone can edit, and lots of links
  • fproxy.tino.i2p: Freenet proxy
  • +
  • echelon.i2p: software archive and information for I2P
  • There are many more eepsites - just follow the links from the ones you see, bookmark your favorites, and visit them often! diff --git a/readme_de.html b/readme_de.html index 1534585dab..e6fd916188 100644 --- a/readme_de.html +++ b/readme_de.html @@ -12,7 +12,8 @@
  • eepsites.i2p: Eine anonym gehostete Suchseite für Eepsites
  • ugha.i2p: Ugha's Eepsite, ein öffentliches Wiki mit vielen Links
  • fproxy.tino.i2p: Freenet proxy
  • - +
  • echelon.i2p: Software Archive und Informationen zu I2P
  • + Es gibt viel mehr Eepsites - folge einfach den Links die du findest, bookmarke Deine Favoriten und besuche sie oft!
  • Im Internet surfen - Es gibt einen HTTP "outproxy" in I2P in Deinem diff --git a/readme_fr.html b/readme_fr.html index 5e619cff2b..f2accf826c 100644 --- a/readme_fr.html +++ b/readme_fr.html @@ -11,6 +11,7 @@
  • eepsites.i2p: un moteur de recherche d'eepsites
  • ugha.i2p: l'eepsite d'ugha, un wiki que chaucun peut éditer ainsi que
  • fproxy.tino.i2p: un proxy Freenet 0.5
  • +
  • echelon.i2p: archive
  • Il y a bien plus d'eepsites - suivez juste les liens au départ de ceux sur lesquels vous êtes, mettez-les dans vos favoris et visitez-les souvent!
  • Parcourez le web - Il y a pour l'instant un outproxy HTTP sur I2P attaché à votre propre proxy HTTP sur le port 4444 - vous devez simplement configurer le proxy de votre navigateur pour l'utiliser (comme expliqué ci-dessus) et aller sur n'importe quel URL normale - vos requêtes seront relayées par le réseau i2p.
  • diff --git a/readme_sv.html b/readme_sv.html index b1f59fa04d..eab177cd14 100644 --- a/readme_sv.html +++ b/readme_sv.html @@ -27,6 +27,7 @@ sökmotor wiki som alla kan förändra, innehåller många länkar
  • fproxy.tino.i2p: Freenet proxy
  • +
  • echelon.i2p: software archive and information for I2P
  • Det finns många fler eepsidor - följ bara länkarna från dom du ser, spara dina favoriter och besök dom ofta! From 0764e19441a43651c144ec9d0d23c0440f34eed2 Mon Sep 17 00:00:00 2001 From: mathiasdm Date: Tue, 7 Apr 2009 17:55:59 +0000 Subject: [PATCH 090/112] New build file for desktopgui. Should work fine for anyone using java 1.6. --- apps/desktopgui/build.xml | 113 +++++++----------- apps/desktopgui/nbproject/build-impl.xml | 25 +--- apps/desktopgui/nbproject/genfiles.properties | 8 +- apps/desktopgui/nbproject/project.properties | 15 ++- .../src/desktopgui/resources/Main.properties | 2 +- 5 files changed, 67 insertions(+), 96 deletions(-) diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml index dbcbab67c9..cf262b2ec4 100644 --- a/apps/desktopgui/build.xml +++ b/apps/desktopgui/build.xml @@ -1,74 +1,51 @@ - - - - - - - - - - - Builds, tests, and runs the project desktopgui. - - + + + + + + + + + + - There exist several targets which are by default empty and which can be - used for execution of your tasks. These targets are usually executed - before and after some main targets. They are: + + + + + + + - -pre-init: called before initialization of project properties - -post-init: called after initialization of project properties - -pre-compile: called before javac compilation - -post-compile: called after javac compilation - -pre-compile-single: called before javac compilation of single file - -post-compile-single: called after javac compilation of single file - -pre-compile-test: called before javac compilation of JUnit tests - -post-compile-test: called after javac compilation of JUnit tests - -pre-compile-test-single: called before javac compilation of single JUnit test - -post-compile-test-single: called after javac compilation of single JUunit test - -pre-jar: called before JAR building - -post-jar: called after JAR building - -post-clean: called after cleaning build products + + + + + + + + + - (Targets beginning with '-' are not intended to be called on their own.) + + + - Example of inserting an obfuscator after compilation could look like this: + + + - - - - - - - For list of available properties check the imported - nbproject/build-impl.xml file. - - - Another way to customize the build is by overriding existing main targets. - The targets of interest are: - - -init-macrodef-javac: defines macro for javac compilation - -init-macrodef-junit: defines macro for junit execution - -init-macrodef-debug: defines macro for class debugging - -init-macrodef-java: defines macro for class execution - -do-jar-with-manifest: JAR building (if you are using a manifest) - -do-jar-without-manifest: JAR building (if you are not using a manifest) - run: execution of project - -javadoc-build: Javadoc generation - test-report: JUnit report generation - - An example of overriding the target for project execution could look like this: - - - - - - - - Notice that the overridden target depends on the jar target and not only on - the compile target as the regular run target does. Again, for a list of available - properties which you can use, check the target you are overriding in the - nbproject/build-impl.xml file. - - --> + + + + + diff --git a/apps/desktopgui/nbproject/build-impl.xml b/apps/desktopgui/nbproject/build-impl.xml index 039f8788f1..f8fea458d1 100644 --- a/apps/desktopgui/nbproject/build-impl.xml +++ b/apps/desktopgui/nbproject/build-impl.xml @@ -152,7 +152,7 @@ is divided into following sections: - + @@ -218,13 +218,13 @@ is divided into following sections: - + - + @@ -255,12 +255,6 @@ is divided into following sections: - - - - - - @@ -270,7 +264,7 @@ is divided into following sections: - + @@ -317,13 +311,6 @@ is divided into following sections: =================== --> - - - - - - - @@ -344,7 +331,7 @@ is divided into following sections: - + @@ -358,7 +345,7 @@ is divided into following sections: - + I2PSnark | - My Eepsite
    + My Eepsite
    I2PTunnel | Tunnels | Profiles | diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java index a80fdc5914..6e9fad1c5e 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java +++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java @@ -76,7 +76,7 @@ public class WebMail extends HttpServlet private static final int BUFSIZE = 4096; - private static final String DEFAULT_HOST = "localhost"; + private static final String DEFAULT_HOST = "127.0.0.1"; private static final int DEFAULT_POP3PORT = 7660; private static final int DEFAULT_SMTPPORT = 7659; diff --git a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java index 4a635fd080..652ff66771 100644 --- a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java +++ b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java @@ -132,7 +132,7 @@ public class SysTray implements SysTrayMenuListener { public void iconLeftClicked(SysTrayMenuEvent e) {} public void iconLeftDoubleClicked(SysTrayMenuEvent e) { - openRouterConsole("http://localhost:" + _portString + "/index.jsp"); + openRouterConsole("http://127.0.0.1:" + _portString + "/index.jsp"); } public void menuItemSelected(SysTrayMenuEvent e) { @@ -153,7 +153,7 @@ public class SysTray implements SysTrayMenuListener { if (!(browser = promptForBrowser("Select browser")).equals("nullnull")) setBrowser(browser); } else if (e.getActionCommand().equals("openconsole")) { - openRouterConsole("http://localhost:" + _portString + "/index.jsp"); + openRouterConsole("http://127.0.0.1:" + _portString + "/index.jsp"); } } diff --git a/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java b/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java index 5487f5faf5..c7524054e7 100644 --- a/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java +++ b/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java @@ -163,7 +163,7 @@ public class UrlLauncher { if (args.length > 0) launcher.openUrl(args[0]); else - launcher.openUrl("http://localhost:7657/index.jsp"); + launcher.openUrl("http://127.0.0.1:7657/index.jsp"); } catch (Exception e) {} } } diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 5b7603fdd6..9e42eef5fa 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -162,7 +162,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa protected void loadConfig(Properties options) { _options = new Properties(); _options.putAll(filter(options)); - _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "localhost"); + _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + ""); try { _portNum = Integer.parseInt(portNum); diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java index 4abdc6fd4d..5f0e8d5e9f 100644 --- a/core/java/src/net/i2p/util/EepGet.java +++ b/core/java/src/net/i2p/util/EepGet.java @@ -18,7 +18,7 @@ import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; /** - * EepGet [-p localhost:4444] + * EepGet [-p 127.0.0.1:4444] * [-n #retries] * [-o outputFile] * [-m markSize lineLen] @@ -123,11 +123,11 @@ public class EepGet { } /** - * EepGet [-p localhost:4444] [-n #retries] [-e etag] [-o outputFile] [-m markSize lineLen] url + * EepGet [-p 127.0.0.1:4444] [-n #retries] [-e etag] [-o outputFile] [-m markSize lineLen] url * */ public static void main(String args[]) { - String proxyHost = "localhost"; + String proxyHost = "127.0.0.1"; int proxyPort = 4444; int numRetries = 5; int markSize = 1024; @@ -212,7 +212,7 @@ public class EepGet { } private static void usage() { - System.err.println("EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] [-t timeout] url"); + System.err.println("EepGet [-p 127.0.0.1:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] [-t timeout] url"); } public static interface StatusListener { diff --git a/installer/resources/ahelper-conflict-header.ht b/installer/resources/ahelper-conflict-header.ht index 3b80e582fe..7f34d21cc3 100644 --- a/installer/resources/ahelper-conflict-header.ht +++ b/installer/resources/ahelper-conflict-header.ht @@ -6,7 +6,7 @@ Proxy-Connection: close Destination key conflict - +
  • eepsites.i2p: an anonymously hosted search engine of eepsites