diff --git a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java index be74de990f2c65fd3d61ae5afbc042c0f1509598..6c95e2e19653f90dc31065232e50a297354ee1c8 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java @@ -25,9 +25,9 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.io.StringReader; import java.util.HashMap; import java.util.Iterator; @@ -35,6 +35,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import net.i2p.util.SecureFileOutputStream; + /** * Utility class providing methods to parse and write files in config file * format, and subscription file format. @@ -277,7 +279,7 @@ public class ConfigParser { */ public static void write(Map map, File file) throws IOException { ConfigParser - .write(map, new BufferedWriter(new FileWriter(file, false))); + .write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); } /** @@ -316,7 +318,7 @@ public class ConfigParser { public static void writeSubscriptions(List list, File file) throws IOException { ConfigParser.writeSubscriptions(list, new BufferedWriter( - new FileWriter(file, false))); + new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); } } diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java index 077acdb7f02bbf7f1a8c13cd912cac599d994078..2588010118eb7156ba390464c942a1d3c4325174 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import net.i2p.I2PAppContext; +import net.i2p.util.SecureDirectory; /** * Main class of addressbook. Performs updates, and runs the main loop. @@ -131,11 +132,11 @@ public class Daemon { String settingsLocation = "config.txt"; File homeFile; if (args.length > 0) { - homeFile = new File(args[0]); + homeFile = new SecureDirectory(args[0]); if (!homeFile.isAbsolute()) - homeFile = new File(I2PAppContext.getGlobalContext().getRouterDir(), args[0]); + homeFile = new SecureDirectory(I2PAppContext.getGlobalContext().getRouterDir(), args[0]); } else { - homeFile = new File(System.getProperty("user.dir")); + homeFile = new SecureDirectory(System.getProperty("user.dir")); } Map defaultSettings = new HashMap(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 127a2bb19abfed633d7d79e440f590bff9a94e4b..6f17a810aba46d7520ff2636a2bb6beaf53b5cc2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -26,6 +26,7 @@ import net.i2p.util.ConcurrentHashSet; import net.i2p.util.EepGet; import net.i2p.util.FileUtil; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; import net.i2p.util.Translate; @@ -77,7 +78,7 @@ public class I2PSnarkUtil { // This is used for both announce replies and .torrent file downloads, // so it must be available even if not connected to I2CP. // so much for multiple instances - _tmpDir = new File(ctx.getTempDir(), "i2psnark"); + _tmpDir = new SecureDirectory(ctx.getTempDir(), "i2psnark"); FileUtil.rmdir(_tmpDir, false); _tmpDir.mkdirs(); } @@ -196,11 +197,14 @@ public class I2PSnarkUtil { /** connect to the given destination */ I2PSocket connect(PeerID peer) throws IOException { - Hash dest = peer.getAddress().calculateHash(); + Destination addr = peer.getAddress(); + if (addr == null) + throw new IOException("Null address"); + Hash dest = addr.calculateHash(); if (_shitlist.contains(dest)) throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted"); try { - I2PSocket rv = _manager.connect(peer.getAddress()); + I2PSocket rv = _manager.connect(addr); if (rv != null) _shitlist.remove(dest); return rv; @@ -224,7 +228,8 @@ public class I2PSnarkUtil { public File get(String url, boolean rewrite) { return get(url, rewrite, 0); } public File get(String url, int retries) { return get(url, true, retries); } public File get(String url, boolean rewrite, int retries) { - _log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy); File out = null; try { // we could use the system tmp dir but deleteOnExit() doesn't seem to work on all platforms... @@ -248,10 +253,12 @@ public class I2PSnarkUtil { } EepGet get = new I2PSocketEepGet(_context, _manager, retries, out.getAbsolutePath(), fetchURL); if (get.fetch()) { - _log.debug("Fetch successful [" + url + "]: size=" + out.length()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Fetch successful [" + url + "]: size=" + out.length()); return out; } else { - _log.warn("Fetch failed [" + url + "]"); + if (_log.shouldLog(Log.WARN)) + _log.warn("Fetch failed [" + url + "]"); out.delete(); return null; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java index f8c8feaede812269b8733c14d21619aa9d3e3b9b..140d3cb468fc024d0f961b928ba3b07b44a3529d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java @@ -109,7 +109,8 @@ public class MetaInfo */ public MetaInfo(Map m) throws InvalidBEncodingException { - _log.debug("Creating a metaInfo: " + m, new Exception("source")); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Creating a metaInfo: " + m, new Exception("source")); BEValue val = (BEValue)m.get("announce"); if (val == null) throw new InvalidBEncodingException("Missing announce string"); @@ -446,14 +447,16 @@ public class MetaInfo else buf.append(val.toString()); } - _log.debug(buf.toString()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug(buf.toString()); byte[] infoBytes = BEncoder.bencode(info); //_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]"); try { MessageDigest digest = MessageDigest.getInstance("SHA"); byte hash[] = digest.digest(infoBytes); - _log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]"); return hash; } catch(NoSuchAlgorithmException nsa) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index e0ae6e1f25449a2b47adf884629ace0d10f5aa66..da1003aaba116089bf2ebd32d4e79af8b5b06e08 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -92,7 +92,8 @@ public class Peer implements Comparable byte[] id = handshake(in, out); this.peerID = new PeerID(id, sock.getPeerDestination()); _id = ++__id; - _log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id)); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id)); } /** @@ -129,7 +130,7 @@ public class Peer implements Comparable @Override public int hashCode() { - return peerID.hashCode() ^ (2 << _id); + return peerID.hashCode() ^ (7777 * (int)_id); } /** @@ -150,6 +151,7 @@ public class Peer implements Comparable /** * Compares the PeerIDs. + * @deprecated unused? */ public int compareTo(Object o) { @@ -181,14 +183,16 @@ public class Peer implements Comparable if (state != null) throw new IllegalStateException("Peer already started"); - _log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting")); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting")); try { // Do we need to handshake? if (din == null) { sock = util.connect(peerID); - _log.debug("Connected to " + peerID + ": " + sock); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Connected to " + peerID + ": " + sock); if ((sock == null) || (sock.isClosed())) { throw new IOException("Unable to reach " + peerID); } @@ -207,14 +211,20 @@ public class Peer implements Comparable // = new BufferedOutputStream(sock.getOutputStream()); byte [] id = handshake(in, out); //handshake(bis, bos); byte [] expected_id = peerID.getID(); - if (!Arrays.equals(expected_id, id)) - throw new IOException("Unexpected peerID '" + if (expected_id == null) { + peerID.setID(id); + } else if (Arrays.equals(expected_id, id)) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Handshake got matching IDs with " + toString()); + } else { + throw new IOException("Unexpected peerID '" + PeerID.idencode(id) + "' expected '" + PeerID.idencode(expected_id) + "'"); - _log.debug("Handshake got matching IDs with " + toString()); + } } else { - _log.debug("Already have din [" + sock + "] with " + toString()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Already have din [" + sock + "] with " + toString()); } PeerConnectionIn in = new PeerConnectionIn(this, din); @@ -229,7 +239,8 @@ public class Peer implements Comparable state = s; listener.connected(this); - _log.debug("Start running the reader with " + toString()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Start running the reader with " + toString()); // Use this thread for running the incomming connection. // The outgoing connection creates its own Thread. out.startup(); @@ -279,7 +290,8 @@ public class Peer implements Comparable dout.write(my_id); dout.flush(); - _log.debug("Wrote my shared hash and ID to " + toString()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Wrote my shared hash and ID to " + toString()); // Handshake read - header byte b = din.readByte(); @@ -306,7 +318,8 @@ public class Peer implements Comparable // Handshake read - peer id din.readFully(bs); - _log.debug("Read the remote side's hash and peerID fully from " + toString()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Read the remote side's hash and peerID fully from " + toString()); return bs; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java b/apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java index c8693843712f0cc89f143aa9f986f9a802aba310..58ef3ae2db01c25582f858fcc26a74a095a46e41 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java @@ -76,10 +76,12 @@ public class PeerAcceptor // is this working right? try { peerInfoHash = readHash(in); - _log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64() - + ": " + Base64.encode(peerInfoHash)); + if (_log.shouldLog(Log.INFO)) + _log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64() + + ": " + Base64.encode(peerInfoHash)); } catch (IOException ioe) { - _log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64()); + if (_log.shouldLog(Log.INFO)) + _log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64()); throw ioe; } in = new SequenceInputStream(new ByteArrayInputStream(peerInfoHash), in); diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 6d38950360761b8ef3cdafa592aea62b66944b88..8f24c864ac9a6c63e0bd8728cc5c587bd47933e0 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -375,7 +375,8 @@ public class PeerCoordinator implements PeerListener if (need_more) { - _log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run")); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run")); // Run the peer with us as listener and the current bitfield. final PeerListener listener = this; diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerID.java b/apps/i2psnark/java/src/org/klomp/snark/PeerID.java index c4a4e9194164dc91b70bdc22c0eada56e9e1f886..37cf1a9b65ccbadad69299d2a783373ce665fc48 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerID.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerID.java @@ -24,19 +24,33 @@ import java.io.IOException; import java.net.UnknownHostException; import java.util.Map; +import net.i2p.I2PAppContext; +import net.i2p.data.Base32; import net.i2p.data.Base64; +import net.i2p.data.DataHelper; import net.i2p.data.Destination; import org.klomp.snark.bencode.BDecoder; import org.klomp.snark.bencode.BEValue; import org.klomp.snark.bencode.InvalidBEncodingException; +/** + * Store the address information about a peer. + * Prior to 0.8.1, an instantiation required a peer ID, and full Destination address. + * Starting with 0.8.1, to support compact tracker responses, + * a PeerID can be instantiated with a Destination Hash alone. + * The full destination lookup is deferred until getAddress() is called, + * and the PeerID is not required. + * Equality is now determined solely by the dest hash. + */ public class PeerID implements Comparable { - private final byte[] id; - private final Destination address; + private byte[] id; + private Destination address; private final int port; - + private byte[] destHash; + /** whether we have tried to get the dest from the hash - only do once */ + private boolean triedDestLookup; private final int hash; public PeerID(byte[] id, Destination address) @@ -44,7 +58,7 @@ public class PeerID implements Comparable this.id = id; this.address = address; this.port = 6881; - + this.destHash = address.calculateHash().getData(); hash = calculateHash(); } @@ -77,17 +91,49 @@ public class PeerID implements Comparable throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]"); port = 6881; - + this.destHash = address.calculateHash().getData(); hash = calculateHash(); } + /** + * Creates a PeerID from a destHash + * @since 0.8.1 + */ + public PeerID(byte[] dest_hash) throws InvalidBEncodingException + { + // id and address remain null + port = 6881; + if (dest_hash.length != 32) + throw new InvalidBEncodingException("bad hash length"); + destHash = dest_hash; + hash = DataHelper.hashCode(dest_hash); + } + public byte[] getID() { return id; } - public Destination getAddress() + /** for connecting out to peer based on desthash @since 0.8.1 */ + public void setID(byte[] xid) { + id = xid; + } + + /** + * Get the destination. + * If this PeerId was instantiated with a destHash, + * and we have not yet done so, lookup the full destination, which may take + * up to 10 seconds. + * @return Dest or null if unknown + */ + public synchronized Destination getAddress() + { + if (address == null && destHash != null && !triedDestLookup) { + String b32 = Base32.encode(destHash) + ".b32.i2p"; + address = I2PAppContext.getGlobalContext().namingService().lookup(b32); + triedDestLookup = true; + } return address; } @@ -96,16 +142,19 @@ public class PeerID implements Comparable return port; } + /** @since 0.8.1 */ + public byte[] getDestHash() + { + return destHash; + } + private int calculateHash() { - int b = 0; - for (int i = 0; i < id.length; i++) - b ^= id[i]; - return (b ^ address.hashCode()) ^ port; + return DataHelper.hashCode(destHash); } /** - * The hash code of a PeerID is the exclusive or of all id bytes. + * The hash code of a PeerID is the hashcode of the desthash */ @Override public int hashCode() @@ -115,18 +164,15 @@ public class PeerID implements Comparable /** * Returns true if and only if this peerID and the given peerID have - * the same 20 bytes as ID. + * the same destination hash */ public boolean sameID(PeerID pid) { - boolean equal = true; - for (int i = 0; equal && i < id.length; i++) - equal = id[i] == pid.id[i]; - return equal; + return DataHelper.eq(destHash, pid.getDestHash()); } /** - * Two PeerIDs are equal when they have the same id, address and port. + * Two PeerIDs are equal when they have the same dest hash */ @Override public boolean equals(Object o) @@ -135,9 +181,7 @@ public class PeerID implements Comparable { PeerID pid = (PeerID)o; - return port == pid.port - && address.equals(pid.address) - && sameID(pid); + return sameID(pid); } else return false; @@ -145,6 +189,7 @@ public class PeerID implements Comparable /** * Compares port, address and id. + * @deprecated unused? and will NPE now that address can be null? */ public int compareTo(Object o) { @@ -176,6 +221,8 @@ public class PeerID implements Comparable @Override public String toString() { + if (id == null || address == null) + return "unkn@" + Base64.encode(destHash).substring(0, 6); int nonZero = 0; for (int i = 0; i < id.length; i++) { if (id[i] != 0) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index c2afec346fd73c3540cef4d2087d0ecafaef6577..9d6cd946000459a9f47c403064f1764c0646308d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -437,7 +437,7 @@ public class Snark try { storage.close(); } catch (IOException ioee) { ioee.printStackTrace(); } - fatal("Could not create storage", ioe); + fatal("Could not check or create storage", ioe); } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 59ab86a12920ec5d2e70ee86fa2fb9df887914e5..b0fe6d1ba7760bfa2148e68c905595b751065d64 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -1,857 +1,880 @@ -package org.klomp.snark; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TreeMap; - -import net.i2p.I2PAppContext; -import net.i2p.data.Base64; -import net.i2p.data.DataHelper; -import net.i2p.util.I2PAppThread; -import net.i2p.util.Log; -import net.i2p.util.OrderedProperties; - -/** - * Manage multiple snarks - */ -public class SnarkManager implements Snark.CompleteListener { - private static SnarkManager _instance = new SnarkManager(); - public static SnarkManager instance() { return _instance; } - - /** map of (canonical) filename of the .torrent file to Snark instance (unsynchronized) */ - private final Map<String, Snark> _snarks; - private final Object _addSnarkLock; - private /* FIXME final FIXME */ File _configFile; - private Properties _config; - private I2PAppContext _context; - private Log _log; - private final List _messages; - private I2PSnarkUtil _util; - private PeerCoordinatorSet _peerCoordinatorSet; - private ConnectionAcceptor _connectionAcceptor; - private Thread _monitor; - private boolean _running; - - public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost"; - public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort"; - public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions"; - //public static final String PROP_EEP_HOST = "i2psnark.eepHost"; - //public static final String PROP_EEP_PORT = "i2psnark.eepPort"; - public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total"; - public static final String PROP_UPBW_MAX = "i2psnark.upbw.max"; - public static final String PROP_DIR = "i2psnark.dir"; - public static final String PROP_META_PREFIX = "i2psnark.zmeta."; - public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; - - private static final String CONFIG_FILE = "i2psnark.config"; - public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops - public static final String DEFAULT_AUTO_START = "false"; - public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix"; - public static final String DEFAULT_LINK_PREFIX = "file:///"; - public static final String PROP_STARTUP_DELAY = "i2psnark.startupDelay"; - - public static final int MIN_UP_BW = 2; - public static final int DEFAULT_MAX_UP_BW = 10; - public static final int DEFAULT_STARTUP_DELAY = 3; - private SnarkManager() { - _snarks = new HashMap(); - _addSnarkLock = new Object(); - _context = I2PAppContext.getGlobalContext(); - _log = _context.logManager().getLog(SnarkManager.class); - _messages = new ArrayList(16); - _util = new I2PSnarkUtil(_context); - _configFile = new File(CONFIG_FILE); - if (!_configFile.isAbsolute()) - _configFile = new File(_context.getConfigDir(), CONFIG_FILE); - loadConfig(null); - } - - /** Caller _must_ call loadConfig(file) before this if setting new values - * for i2cp host/port or i2psnark.dir - */ - public void start() { - _running = true; - _peerCoordinatorSet = new PeerCoordinatorSet(); - _connectionAcceptor = new ConnectionAcceptor(_util); - int minutes = getStartupDelayMinutes(); - _messages.add(_("Adding torrents in {0} minutes", minutes)); - _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); - _monitor.start(); - _context.addShutdownTask(new SnarkManagerShutdown()); - } - - public void stop() { - _running = false; - _monitor.interrupt(); - _connectionAcceptor.halt(); - (new SnarkManagerShutdown()).run(); - } - - /** hook to I2PSnarkUtil for the servlet */ - public I2PSnarkUtil util() { return _util; } - - private static final int MAX_MESSAGES = 5; - public void addMessage(String message) { - synchronized (_messages) { - _messages.add(message); - while (_messages.size() > MAX_MESSAGES) - _messages.remove(0); - } - if (_log.shouldLog(Log.INFO)) - _log.info("MSG: " + message); - } - - /** newest last */ - public List getMessages() { - synchronized (_messages) { - return new ArrayList(_messages); - } - } - - public boolean shouldAutoStart() { - return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue(); - } - public String linkPrefix() { - return _config.getProperty(PROP_LINK_PREFIX, DEFAULT_LINK_PREFIX + getDataDir().getAbsolutePath() + File.separatorChar); - } - private int getStartupDelayMinutes() { - return Integer.valueOf(_config.getProperty(PROP_STARTUP_DELAY)).intValue(); - } - public File getDataDir() { - String dir = _config.getProperty(PROP_DIR, "i2psnark"); - File f = new File(dir); - if (!f.isAbsolute()) - f = new File(_context.getAppDir(), dir); - return f; - } - - /** null to set initial defaults */ - public void loadConfig(String filename) { - if (_config == null) - _config = new OrderedProperties(); - if (filename != null) { - File cfg = new File(filename); - if (!cfg.isAbsolute()) - cfg = new File(_context.getConfigDir(), filename); - _configFile = cfg; - if (cfg.exists()) { - try { - DataHelper.loadProps(_config, cfg); - } catch (IOException ioe) { - _log.error("Error loading I2PSnark config '" + filename + "'", ioe); - } - } - } - // now add sane defaults - if (!_config.containsKey(PROP_I2CP_HOST)) - _config.setProperty(PROP_I2CP_HOST, "127.0.0.1"); - if (!_config.containsKey(PROP_I2CP_PORT)) - _config.setProperty(PROP_I2CP_PORT, "7654"); - if (!_config.containsKey(PROP_I2CP_OPTS)) - _config.setProperty(PROP_I2CP_OPTS, "inbound.length=2 inbound.lengthVariance=0 outbound.length=2 outbound.lengthVariance=0 inbound.quantity=3 outbound.quantity=3"); - //if (!_config.containsKey(PROP_EEP_HOST)) - // _config.setProperty(PROP_EEP_HOST, "127.0.0.1"); - //if (!_config.containsKey(PROP_EEP_PORT)) - // _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_DIR)) - _config.setProperty(PROP_DIR, "i2psnark"); - if (!_config.containsKey(PROP_AUTO_START)) - _config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START); - if (!_config.containsKey(PROP_STARTUP_DELAY)) - _config.setProperty(PROP_STARTUP_DELAY, "" + DEFAULT_STARTUP_DELAY); - - 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); - int i2cpPort = getInt(PROP_I2CP_PORT, 7654); - String opts = _config.getProperty(PROP_I2CP_OPTS); - Map i2cpOpts = new HashMap(); - if (opts != null) { - StringTokenizer tok = new StringTokenizer(opts, " "); - while (tok.hasMoreTokens()) { - String pair = tok.nextToken(); - int split = pair.indexOf('='); - if (split > 0) - i2cpOpts.put(pair.substring(0, split), pair.substring(split+1)); - } - } - if (i2cpHost != null) { - _util.setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts); - _log.debug("Configuring with I2CP options " + i2cpOpts); - } - //I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties()); - //String eepHost = _config.getProperty(PROP_EEP_HOST); - //int eepPort = getInt(PROP_EEP_PORT, 4444); - //if (eepHost != null) - // _util.setProxy(eepHost, eepPort); - _util.setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS)); - _util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW)); - _util.setStartupDelay(getInt(PROP_STARTUP_DELAY, DEFAULT_STARTUP_DELAY)); - String ot = _config.getProperty(I2PSnarkUtil.PROP_OPENTRACKERS); - if (ot != null) - _util.setOpenTrackerString(ot); - // FIXME set util use open trackers property somehow - getDataDir().mkdirs(); - } - - private int getInt(String prop, int defaultVal) { - String p = _config.getProperty(prop); - try { - if ( (p != null) && (p.trim().length() > 0) ) - return Integer.parseInt(p.trim()); - } catch (NumberFormatException nfe) { - // ignore - } - return defaultVal; - } - - public void updateConfig(String dataDir, boolean autoStart, String startDelay, String seedPct, String eepHost, - String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts, - String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) { - boolean changed = false; - //if (eepHost != null) { - // // unused, we use socket eepget - // int port = _util.getEepProxyPort(); - // try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {} - // String host = _util.getEepProxyHost(); - // if ( (eepHost.trim().length() > 0) && (port > 0) && - // ((!host.equals(eepHost) || (port != _util.getEepProxyPort()) )) ) { - // _util.setProxy(eepHost, port); - // changed = true; - // _config.setProperty(PROP_EEP_HOST, eepHost); - // _config.setProperty(PROP_EEP_PORT, eepPort+""); - // addMessage("EepProxy location changed to " + eepHost + ":" + port); - // } - //} - if (upLimit != null) { - int limit = _util.getMaxUploaders(); - try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {} - if ( limit != _util.getMaxUploaders()) { - if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) { - _util.setMaxUploaders(limit); - changed = true; - _config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit); - addMessage(_("Total uploaders limit changed to {0}", limit)); - } else { - addMessage(_("Minimum total uploaders limit is {0}", Snark.MIN_TOTAL_UPLOADERS)); - } - } - } - if (upBW != null) { - int limit = _util.getMaxUpBW(); - try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {} - if ( limit != _util.getMaxUpBW()) { - if ( limit >= MIN_UP_BW ) { - _util.setMaxUpBW(limit); - changed = true; - _config.setProperty(PROP_UPBW_MAX, "" + limit); - addMessage(_("Up BW limit changed to {0}KBps", limit)); - } else { - addMessage(_("Minimum up bandwidth limit is {0}KBps", MIN_UP_BW)); - } - } - } - - if (startDelay != null){ - int minutes = _util.getStartupDelay(); - try { minutes = Integer.parseInt(startDelay); } catch (NumberFormatException nfe) {} - if ( minutes != _util.getStartupDelay()) { - _util.setStartupDelay(minutes); - changed = true; - _config.setProperty(PROP_STARTUP_DELAY, "" + minutes); - addMessage(_("Startup delay limit changed to {0} minutes", minutes)); - } - - } - if (i2cpHost != null) { - int oldI2CPPort = _util.getI2CPPort(); - String oldI2CPHost = _util.getI2CPHost(); - int port = oldI2CPPort; - try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {} - String host = oldI2CPHost; - Map opts = new HashMap(); - if (i2cpOpts == null) i2cpOpts = ""; - StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n"); - while (tok.hasMoreTokens()) { - String pair = tok.nextToken(); - int split = pair.indexOf('='); - if (split > 0) - opts.put(pair.substring(0, split), pair.substring(split+1)); - } - Map oldOpts = new HashMap(); - String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS); - if (oldI2CPOpts == null) oldI2CPOpts = ""; - tok = new StringTokenizer(oldI2CPOpts, " \t\n"); - while (tok.hasMoreTokens()) { - String pair = tok.nextToken(); - int split = pair.indexOf('='); - if (split > 0) - oldOpts.put(pair.substring(0, split), pair.substring(split+1)); - } - - if ( (i2cpHost.trim().length() > 0) && (port > 0) && - ((!host.equals(i2cpHost) || - (port != _util.getI2CPPort()) || - (!oldOpts.equals(opts)))) ) { - boolean snarksActive = false; - Set names = listTorrentFiles(); - for (Iterator iter = names.iterator(); iter.hasNext(); ) { - Snark snark = getTorrent((String)iter.next()); - if ( (snark != null) && (!snark.stopped) ) { - snarksActive = true; - break; - } - } - if (snarksActive) { - Properties p = new Properties(); - p.putAll(opts); - _util.setI2CPConfig(i2cpHost, port, p); - addMessage(_("I2CP and tunnel changes will take effect after stopping all torrents")); - _log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts - + "] oldOpts [" + oldOpts + "]"); - } else { - if (_util.connected()) { - _util.disconnect(); - addMessage(_("Disconnecting old I2CP destination")); - } - Properties p = new Properties(); - p.putAll(opts); - addMessage(_("I2CP settings changed to {0}", i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")")); - _util.setI2CPConfig(i2cpHost, port, p); - boolean ok = _util.connect(); - if (!ok) { - addMessage(_("Unable to connect with the new settings, reverting to the old I2CP settings")); - _util.setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts); - ok = _util.connect(); - if (!ok) - addMessage(_("Unable to reconnect with the old settings!")); - } else { - addMessage(_("Reconnected on the new I2CP destination")); - _config.setProperty(PROP_I2CP_HOST, i2cpHost.trim()); - _config.setProperty(PROP_I2CP_PORT, "" + port); - _config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim()); - changed = true; - // no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive - for (Iterator iter = names.iterator(); iter.hasNext(); ) { - String name = (String)iter.next(); - Snark snark = getTorrent(name); - if ( (snark != null) && (snark.acceptor != null) ) { - snark.acceptor.restart(); - addMessage(_("I2CP listener restarted for \"{0}\"", snark.meta.getName())); - } - } - } - } - changed = true; - } - } - if (shouldAutoStart() != autoStart) { - _config.setProperty(PROP_AUTO_START, autoStart + ""); - if (autoStart) - addMessage(_("Enabled autostart")); - else - addMessage(_("Disabled autostart")); - changed = true; - } - if (_util.shouldUseOpenTrackers() != useOpenTrackers) { - _config.setProperty(I2PSnarkUtil.PROP_USE_OPENTRACKERS, useOpenTrackers + ""); - if (useOpenTrackers) - addMessage(_("Enabled open trackers - torrent restart required to take effect.")); - else - addMessage(_("Disabled open trackers - torrent restart required to take effect.")); - changed = true; - } - if (openTrackers != null) { - if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(_util.getOpenTrackerString())) { - _config.setProperty(I2PSnarkUtil.PROP_OPENTRACKERS, openTrackers.trim()); - _util.setOpenTrackerString(openTrackers); - addMessage(_("Open Tracker list changed - torrent restart required to take effect.")); - changed = true; - } - } - if (changed) { - saveConfig(); - } else { - addMessage(_("Configuration unchanged.")); - } - } - - public void saveConfig() { - try { - synchronized (_configFile) { - DataHelper.storeProps(_config, _configFile); - } - } catch (IOException ioe) { - addMessage(_("Unable to save the config to {0}", _configFile.getAbsolutePath())); - } - } - - public Properties getConfig() { return _config; } - - /** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */ - private static final int MAX_FILES_PER_TORRENT = 512; - - /** set of canonical .torrent filenames that we are dealing with */ - public Set<String> listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } } - - /** - * Grab the torrent given the (canonical) filename of the .torrent file - * @return Snark or null - */ - public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } } - - /** - * Grab the torrent given the base name of the storage - * @return Snark or null - * @since 0.7.14 - */ - public Snark getTorrentByBaseName(String filename) { - synchronized (_snarks) { - for (Snark s : _snarks.values()) { - if (s.storage.getBaseName().equals(filename)) - return s; - } - } - return null; - } - - public void addTorrent(String filename) { addTorrent(filename, false); } - public void addTorrent(String filename, boolean dontAutoStart) { - if ((!dontAutoStart) && !_util.connected()) { - addMessage(_("Connecting to I2P")); - boolean ok = _util.connect(); - if (!ok) { - addMessage(_("Error connecting to I2P - check your I2CP settings!")); - return; - } - } - File sfile = new File(filename); - try { - filename = sfile.getCanonicalPath(); - } catch (IOException ioe) { - _log.error("Unable to add the torrent " + filename, ioe); - addMessage(_("Error: Could not add the torrent {0}", filename) + ": " + ioe.getMessage()); - return; - } - File dataDir = getDataDir(); - Snark torrent = null; - synchronized (_snarks) { - torrent = (Snark)_snarks.get(filename); - } - // don't hold the _snarks lock while verifying the torrent - if (torrent == null) { - synchronized (_addSnarkLock) { - // double-check - synchronized (_snarks) { - if(_snarks.get(filename) != null) - return; - } - - FileInputStream fis = null; - try { - fis = new FileInputStream(sfile); - } catch (IOException ioe) { - // catch this here so we don't try do delete it below - addMessage(_("Cannot open \"{0}\"", sfile.getName()) + ": " + ioe.getMessage()); - return; - } - - try { - MetaInfo info = new MetaInfo(fis); - try { - fis.close(); - fis = null; - } catch (IOException e) {} - - if (!TrackerClient.isValidAnnounce(info.getAnnounce())) { - if (_util.shouldUseOpenTrackers() && _util.getOpenTrackers() != null) { - addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only", info.getName())); - } else { - addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!", info.getName())); - dontAutoStart = true; - } - } - String rejectMessage = locked_validateTorrent(info); - if (rejectMessage != null) { - sfile.delete(); - addMessage(rejectMessage); - return; - } else { - torrent = new Snark(_util, filename, null, -1, null, null, this, - _peerCoordinatorSet, _connectionAcceptor, - false, dataDir.getPath()); - torrent.completeListener = this; - synchronized (_snarks) { - _snarks.put(filename, torrent); - } - } - } catch (IOException ioe) { - addMessage(_("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage()); - if (sfile.exists()) - sfile.delete(); - return; - } finally { - if (fis != null) try { fis.close(); } catch (IOException ioe) {} - } - } - } else { - return; - } - // ok, snark created, now lets start it up or configure it further - File f = new File(filename); - if (!dontAutoStart && shouldAutoStart()) { - torrent.startTorrent(); - addMessage(_("Torrent added and started: \"{0}\"", f.getName())); - } else { - addMessage(_("Torrent added: \"{0}\"", f.getName())); - } - } - - /** - * Get the timestamp for a torrent from the config file - */ - public long getSavedTorrentTime(Snark snark) { - MetaInfo metainfo = snark.meta; - byte[] ih = metainfo.getInfoHash(); - String infohash = Base64.encode(ih); - infohash = infohash.replace('=', '$'); - String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); - if (time == null) - return 0; - int comma = time.indexOf(','); - if (comma <= 0) - return 0; - time = time.substring(0, comma); - try { return Long.parseLong(time); } catch (NumberFormatException nfe) {} - return 0; - } - - /** - * Get the saved bitfield for a torrent from the config file. - * Convert "." to a full bitfield. - */ - public BitField getSavedTorrentBitField(Snark snark) { - MetaInfo metainfo = snark.meta; - byte[] ih = metainfo.getInfoHash(); - String infohash = Base64.encode(ih); - infohash = infohash.replace('=', '$'); - String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); - if (bf == null) - return null; - int comma = bf.indexOf(','); - if (comma <= 0) - return null; - bf = bf.substring(comma + 1).trim(); - int len = metainfo.getPieces(); - if (bf.equals(".")) { - BitField bitfield = new BitField(len); - for (int i = 0; i < len; i++) - bitfield.set(i); - return bitfield; - } - byte[] bitfield = Base64.decode(bf); - if (bitfield == null) - return null; - if (bitfield.length * 8 < len) - return null; - return new BitField(bitfield, len); - } - - /** - * Save the completion status of a torrent and the current time in the config file - * in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield". - * The config file property key is appended with the Base64 of the infohash, - * with the '=' changed to '$' since a key can't contain '='. - * The time is a standard long converted to string. - * The status is either a bitfield converted to Base64 or "." for a completed - * torrent to save space in the config file and in memory. - */ - public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) { - byte[] ih = metainfo.getInfoHash(); - String infohash = Base64.encode(ih); - infohash = infohash.replace('=', '$'); - String now = "" + System.currentTimeMillis(); - String bfs; - if (bitfield.complete()) { - bfs = "."; - } else { - byte[] bf = bitfield.getFieldBytes(); - bfs = Base64.encode(bf); - } - _config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs); - saveConfig(); - } - - /** - * Remove the status of a torrent from the config file. - * This may help the config file from growing too big. - */ - public void removeTorrentStatus(MetaInfo metainfo) { - byte[] ih = metainfo.getInfoHash(); - String infohash = Base64.encode(ih); - infohash = infohash.replace('=', '$'); - _config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); - saveConfig(); - } - - /** - * Warning - does not validate announce URL - use TrackerClient.isValidAnnounce() - */ - private String locked_validateTorrent(MetaInfo info) throws IOException { - List files = info.getFiles(); - if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) { - return _("Too many files in \"{0}\" ({1}), deleting it!", info.getName(), files.size()); - } else if ( (files == null) && (info.getName().endsWith(".torrent")) ) { - return _("Torrent file \"{0}\" cannot end in \".torrent\", deleting it!", info.getName()); - } else if (info.getPieces() <= 0) { - return _("No pieces in \"{0}\", deleting it!", info.getName()); - } else if (info.getPieces() > Storage.MAX_PIECES) { - return _("Too many pieces in \"{0}\", limit is {1}, deleting it!", info.getName(), Storage.MAX_PIECES); - } else if (info.getPieceLength(0) > Storage.MAX_PIECE_SIZE) { - return _("Pieces are too large in \"{0}\" ({1}B), deleting it.", info.getName(), DataHelper.formatSize2(info.getPieceLength(0))) + ' ' + - _("Limit is {0}B", DataHelper.formatSize2(Storage.MAX_PIECE_SIZE)); - } else if (info.getTotalLength() > Storage.MAX_TOTAL_SIZE) { - System.out.println("torrent info: " + info.toString()); - List lengths = info.getLengths(); - if (lengths != null) - for (int i = 0; i < lengths.size(); i++) - System.out.println("File " + i + " is " + lengths.get(i) + " long."); - - return _("Torrents larger than {0}B are not supported yet, deleting \"{1}\"", Storage.MAX_TOTAL_SIZE, info.getName()); - } else { - // ok - return null; - } - } - - /** - * Stop the torrent, leaving it on the list of torrents unless told to remove it - */ - public Snark stopTorrent(String filename, boolean shouldRemove) { - File sfile = new File(filename); - try { - filename = sfile.getCanonicalPath(); - } catch (IOException ioe) { - _log.error("Unable to remove the torrent " + filename, ioe); - addMessage(_("Error: Could not remove the torrent {0}", filename) + ": " + ioe.getMessage()); - return null; - } - int remaining = 0; - Snark torrent = null; - synchronized (_snarks) { - if (shouldRemove) - torrent = (Snark)_snarks.remove(filename); - else - torrent = (Snark)_snarks.get(filename); - remaining = _snarks.size(); - } - if (torrent != null) { - boolean wasStopped = torrent.stopped; - torrent.stopTorrent(); - if (remaining == 0) { - // should we disconnect/reconnect here (taking care to deal with the other thread's - // I2PServerSocket.accept() call properly?) - ////_util. - } - if (!wasStopped) - addMessage(_("Torrent stopped: \"{0}\"", sfile.getName())); - } - return torrent; - } - /** - * Stop the torrent and delete the torrent file itself, but leaving the data - * behind. - */ - public void removeTorrent(String filename) { - Snark torrent = stopTorrent(filename, true); - if (torrent != null) { - File torrentFile = new File(filename); - torrentFile.delete(); - if (torrent.storage != null) - removeTorrentStatus(torrent.storage.getMetaInfo()); - addMessage(_("Torrent removed: \"{0}\"", torrentFile.getName())); - } - } - - private class DirMonitor implements Runnable { - public void run() { - try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {} - // the first message was a "We are starting up in 1m" - synchronized (_messages) { - if (_messages.size() == 1) - _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()); - try { - monitorTorrents(dir); - } catch (Exception e) { - _log.error("Error in the DirectoryMonitor", e); - } - try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} - } - } - } - - /** two listeners */ - public void torrentComplete(Snark snark) { - File f = new File(snark.torrent); - long len = snark.meta.getTotalLength(); - addMessage(_("Download finished: \"{0}\"", f.getName()) + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')'); - updateStatus(snark); - } - - public void updateStatus(Snark snark) { - saveTorrentStatus(snark.meta, snark.storage.getBitField()); - } - - private void monitorTorrents(File dir) { - String fileNames[] = dir.list(TorrentFilenameFilter.instance()); - List foundNames = new ArrayList(0); - if (fileNames != null) { - for (int i = 0; i < fileNames.length; i++) { - try { - foundNames.add(new File(dir, fileNames[i]).getCanonicalPath()); - } catch (IOException ioe) { - _log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe); - } - } - } - - Set existingNames = listTorrentFiles(); - // lets find new ones first... - for (int i = 0; i < foundNames.size(); i++) { - if (existingNames.contains(foundNames.get(i))) { - // already known. noop - } else { - if (shouldAutoStart() && !_util.connect()) - addMessage(_("Unable to connect to I2P!")); - addTorrent((String)foundNames.get(i), !shouldAutoStart()); - } - } - // now lets see which ones have been removed... - for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) { - String name = (String)iter.next(); - if (foundNames.contains(name)) { - // known and still there. noop - } else { - // known, but removed. drop it - stopTorrent(name, true); - } - } - } - - /** translate */ - private String _(String s) { - return _util.getString(s); - } - - /** translate */ - private String _(String s, Object o) { - return _util.getString(s, o); - } - - /** translate */ - private String _(String s, Object o, Object o2) { - return _util.getString(s, o, o2); - } - - /** - * "name", "announceURL=websiteURL" pairs - */ - private static final String DEFAULT_TRACKERS[] = { -// "Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/" -// , "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/" -// , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/" -// , "NickyB", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php=http://nickyb.i2p/bittorrent/" -// , "Orion", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php=http://orion.i2p/bt/" -// , "anonymity", "http://8EoJZIKrWgGuDrxA3nRJs1jsPfiGwmFWL91hBrf0HA7oKhEvAna4Ocx47VLUR9retVEYBAyWFK-eZTPcvhnz9XffBEiJQQ~kFSCqb1fV6IfPiV3HySqi9U5Caf6~hC46fRd~vYnxmaBLICT3N160cxBETqH3v2rdxdJpvYt8q4nMk9LUeVXq7zqCTFLLG5ig1uKgNzBGe58iNcsvTEYlnbYcE930ABmrzj8G1qQSgSwJ6wx3tUQNl1z~4wSOUMan~raZQD60lRK70GISjoX0-D0Po9WmPveN3ES3g72TIET3zc3WPdK2~lgmKGIs8GgNLES1cXTolvbPhdZK1gxddRMbJl6Y6IPFyQ9o4-6Rt3Lp-RMRWZ2TG7j2OMcNSiOmATUhKEFBDfv-~SODDyopGBmfeLw16F4NnYednvn4qP10dyMHcUASU6Zag4mfc2-WivrOqeWhD16fVAh8MoDpIIT~0r9XmwdaVFyLcjbXObabJczxCAW3fodQUnvuSkwzAAAA.i2p/anonymityTracker/announce.php=http://anonymityweb.i2p/anonymityTracker/" -// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php" -// , "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/" -// , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/" - "POSTMAN", "http://tracker2.postman.i2p/announce.php=http://tracker2.postman.i2p/" - ,"WELTERDE", "http://tracker.welterde.i2p/a=http://tracker.welterde.i2p/stats?mode=top5" - , "CRSTRACK", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/" - }; - - /** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */ - public static final String PROP_TRACKERS = "i2psnark.trackers"; - private static Map trackerMap = null; - /** sorted map of name to announceURL=baseURL */ - public Map getTrackers() { - if (trackerMap != null) // only do this once, can't be updated while running - return trackerMap; - Map rv = new TreeMap(); - String trackers = _config.getProperty(PROP_TRACKERS); - if ( (trackers == null) || (trackers.trim().length() <= 0) ) - trackers = _context.getProperty(PROP_TRACKERS); - if ( (trackers == null) || (trackers.trim().length() <= 0) ) { - for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2) - rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]); - } else { - StringTokenizer tok = new StringTokenizer(trackers, ","); - while (tok.hasMoreTokens()) { - String pair = tok.nextToken(); - int split = pair.indexOf('='); - if (split <= 0) - continue; - String name = pair.substring(0, split).trim(); - String url = pair.substring(split+1).trim(); - if ( (name.length() > 0) && (url.length() > 0) ) - rv.put(name, url); - } - } - - trackerMap = rv; - return trackerMap; - } - - private static class TorrentFilenameFilter implements FilenameFilter { - private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter(); - public static TorrentFilenameFilter instance() { return _filter; } - public boolean accept(File dir, String name) { - return (name != null) && (name.endsWith(".torrent")); - } - } - - public class SnarkManagerShutdown extends I2PAppThread { - @Override - public void run() { - Set names = listTorrentFiles(); - for (Iterator iter = names.iterator(); iter.hasNext(); ) { - Snark snark = getTorrent((String)iter.next()); - if ( (snark != null) && (!snark.stopped) ) - snark.stopTorrent(); - } - } - } -} +package org.klomp.snark; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeMap; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; +import net.i2p.util.I2PAppThread; +import net.i2p.util.Log; +import net.i2p.util.OrderedProperties; +import net.i2p.util.SecureDirectory; + +/** + * Manage multiple snarks + */ +public class SnarkManager implements Snark.CompleteListener { + private static SnarkManager _instance = new SnarkManager(); + public static SnarkManager instance() { return _instance; } + + /** map of (canonical) filename of the .torrent file to Snark instance (unsynchronized) */ + private final Map<String, Snark> _snarks; + private final Object _addSnarkLock; + private /* FIXME final FIXME */ File _configFile; + private Properties _config; + private I2PAppContext _context; + private Log _log; + private final List _messages; + private I2PSnarkUtil _util; + private PeerCoordinatorSet _peerCoordinatorSet; + private ConnectionAcceptor _connectionAcceptor; + private Thread _monitor; + private boolean _running; + + public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost"; + public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort"; + public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions"; + //public static final String PROP_EEP_HOST = "i2psnark.eepHost"; + //public static final String PROP_EEP_PORT = "i2psnark.eepPort"; + public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total"; + public static final String PROP_UPBW_MAX = "i2psnark.upbw.max"; + public static final String PROP_DIR = "i2psnark.dir"; + public static final String PROP_META_PREFIX = "i2psnark.zmeta."; + public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; + + private static final String CONFIG_FILE = "i2psnark.config"; + public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops + public static final String DEFAULT_AUTO_START = "false"; + public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix"; + public static final String DEFAULT_LINK_PREFIX = "file:///"; + public static final String PROP_STARTUP_DELAY = "i2psnark.startupDelay"; + + public static final int MIN_UP_BW = 2; + public static final int DEFAULT_MAX_UP_BW = 10; + public static final int DEFAULT_STARTUP_DELAY = 3; + private SnarkManager() { + _snarks = new HashMap(); + _addSnarkLock = new Object(); + _context = I2PAppContext.getGlobalContext(); + _log = _context.logManager().getLog(SnarkManager.class); + _messages = new ArrayList(16); + _util = new I2PSnarkUtil(_context); + _configFile = new File(CONFIG_FILE); + if (!_configFile.isAbsolute()) + _configFile = new File(_context.getConfigDir(), CONFIG_FILE); + loadConfig(null); + } + + /** Caller _must_ call loadConfig(file) before this if setting new values + * for i2cp host/port or i2psnark.dir + */ + public void start() { + _running = true; + _peerCoordinatorSet = new PeerCoordinatorSet(); + _connectionAcceptor = new ConnectionAcceptor(_util); + int minutes = getStartupDelayMinutes(); + _messages.add(_("Adding torrents in {0} minutes", minutes)); + _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); + _monitor.start(); + _context.addShutdownTask(new SnarkManagerShutdown()); + } + + public void stop() { + _running = false; + _monitor.interrupt(); + _connectionAcceptor.halt(); + (new SnarkManagerShutdown()).run(); + } + + /** hook to I2PSnarkUtil for the servlet */ + public I2PSnarkUtil util() { return _util; } + + private static final int MAX_MESSAGES = 5; + public void addMessage(String message) { + synchronized (_messages) { + _messages.add(message); + while (_messages.size() > MAX_MESSAGES) + _messages.remove(0); + } + if (_log.shouldLog(Log.INFO)) + _log.info("MSG: " + message); + } + + /** newest last */ + public List getMessages() { + synchronized (_messages) { + return new ArrayList(_messages); + } + } + + public boolean shouldAutoStart() { + return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue(); + } + public String linkPrefix() { + return _config.getProperty(PROP_LINK_PREFIX, DEFAULT_LINK_PREFIX + getDataDir().getAbsolutePath() + File.separatorChar); + } + private int getStartupDelayMinutes() { + return Integer.valueOf(_config.getProperty(PROP_STARTUP_DELAY)).intValue(); + } + public File getDataDir() { + String dir = _config.getProperty(PROP_DIR, "i2psnark"); + File f = new SecureDirectory(dir); + if (!f.isAbsolute()) + f = new SecureDirectory(_context.getAppDir(), dir); + return f; + } + + /** null to set initial defaults */ + public void loadConfig(String filename) { + if (_config == null) + _config = new OrderedProperties(); + if (filename != null) { + File cfg = new File(filename); + if (!cfg.isAbsolute()) + cfg = new File(_context.getConfigDir(), filename); + _configFile = cfg; + if (cfg.exists()) { + try { + DataHelper.loadProps(_config, cfg); + } catch (IOException ioe) { + _log.error("Error loading I2PSnark config '" + filename + "'", ioe); + } + } + } + // now add sane defaults + if (!_config.containsKey(PROP_I2CP_HOST)) + _config.setProperty(PROP_I2CP_HOST, "127.0.0.1"); + if (!_config.containsKey(PROP_I2CP_PORT)) + _config.setProperty(PROP_I2CP_PORT, "7654"); + if (!_config.containsKey(PROP_I2CP_OPTS)) + _config.setProperty(PROP_I2CP_OPTS, "inbound.length=2 inbound.lengthVariance=0 outbound.length=2 outbound.lengthVariance=0 inbound.quantity=3 outbound.quantity=3"); + //if (!_config.containsKey(PROP_EEP_HOST)) + // _config.setProperty(PROP_EEP_HOST, "127.0.0.1"); + //if (!_config.containsKey(PROP_EEP_PORT)) + // _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_DIR)) + _config.setProperty(PROP_DIR, "i2psnark"); + if (!_config.containsKey(PROP_AUTO_START)) + _config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START); + if (!_config.containsKey(PROP_STARTUP_DELAY)) + _config.setProperty(PROP_STARTUP_DELAY, "" + DEFAULT_STARTUP_DELAY); + + 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); + int i2cpPort = getInt(PROP_I2CP_PORT, 7654); + String opts = _config.getProperty(PROP_I2CP_OPTS); + Map i2cpOpts = new HashMap(); + if (opts != null) { + StringTokenizer tok = new StringTokenizer(opts, " "); + while (tok.hasMoreTokens()) { + String pair = tok.nextToken(); + int split = pair.indexOf('='); + if (split > 0) + i2cpOpts.put(pair.substring(0, split), pair.substring(split+1)); + } + } + if (i2cpHost != null) { + _util.setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Configuring with I2CP options " + i2cpOpts); + } + //I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties()); + //String eepHost = _config.getProperty(PROP_EEP_HOST); + //int eepPort = getInt(PROP_EEP_PORT, 4444); + //if (eepHost != null) + // _util.setProxy(eepHost, eepPort); + _util.setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS)); + _util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW)); + _util.setStartupDelay(getInt(PROP_STARTUP_DELAY, DEFAULT_STARTUP_DELAY)); + String ot = _config.getProperty(I2PSnarkUtil.PROP_OPENTRACKERS); + if (ot != null) + _util.setOpenTrackerString(ot); + // FIXME set util use open trackers property somehow + getDataDir().mkdirs(); + } + + private int getInt(String prop, int defaultVal) { + String p = _config.getProperty(prop); + try { + if ( (p != null) && (p.trim().length() > 0) ) + return Integer.parseInt(p.trim()); + } catch (NumberFormatException nfe) { + // ignore + } + return defaultVal; + } + + public void updateConfig(String dataDir, boolean autoStart, String startDelay, String seedPct, String eepHost, + String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts, + String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) { + boolean changed = false; + //if (eepHost != null) { + // // unused, we use socket eepget + // int port = _util.getEepProxyPort(); + // try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {} + // String host = _util.getEepProxyHost(); + // if ( (eepHost.trim().length() > 0) && (port > 0) && + // ((!host.equals(eepHost) || (port != _util.getEepProxyPort()) )) ) { + // _util.setProxy(eepHost, port); + // changed = true; + // _config.setProperty(PROP_EEP_HOST, eepHost); + // _config.setProperty(PROP_EEP_PORT, eepPort+""); + // addMessage("EepProxy location changed to " + eepHost + ":" + port); + // } + //} + if (upLimit != null) { + int limit = _util.getMaxUploaders(); + try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {} + if ( limit != _util.getMaxUploaders()) { + if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) { + _util.setMaxUploaders(limit); + changed = true; + _config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit); + addMessage(_("Total uploaders limit changed to {0}", limit)); + } else { + addMessage(_("Minimum total uploaders limit is {0}", Snark.MIN_TOTAL_UPLOADERS)); + } + } + } + if (upBW != null) { + int limit = _util.getMaxUpBW(); + try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {} + if ( limit != _util.getMaxUpBW()) { + if ( limit >= MIN_UP_BW ) { + _util.setMaxUpBW(limit); + changed = true; + _config.setProperty(PROP_UPBW_MAX, "" + limit); + addMessage(_("Up BW limit changed to {0}KBps", limit)); + } else { + addMessage(_("Minimum up bandwidth limit is {0}KBps", MIN_UP_BW)); + } + } + } + + if (startDelay != null){ + int minutes = _util.getStartupDelay(); + try { minutes = Integer.parseInt(startDelay); } catch (NumberFormatException nfe) {} + if ( minutes != _util.getStartupDelay()) { + _util.setStartupDelay(minutes); + changed = true; + _config.setProperty(PROP_STARTUP_DELAY, "" + minutes); + addMessage(_("Startup delay limit changed to {0} minutes", minutes)); + } + + } + if (i2cpHost != null) { + int oldI2CPPort = _util.getI2CPPort(); + String oldI2CPHost = _util.getI2CPHost(); + int port = oldI2CPPort; + try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {} + String host = oldI2CPHost; + Map opts = new HashMap(); + if (i2cpOpts == null) i2cpOpts = ""; + StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n"); + while (tok.hasMoreTokens()) { + String pair = tok.nextToken(); + int split = pair.indexOf('='); + if (split > 0) + opts.put(pair.substring(0, split), pair.substring(split+1)); + } + Map oldOpts = new HashMap(); + String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS); + if (oldI2CPOpts == null) oldI2CPOpts = ""; + tok = new StringTokenizer(oldI2CPOpts, " \t\n"); + while (tok.hasMoreTokens()) { + String pair = tok.nextToken(); + int split = pair.indexOf('='); + if (split > 0) + oldOpts.put(pair.substring(0, split), pair.substring(split+1)); + } + + if ( (i2cpHost.trim().length() > 0) && (port > 0) && + ((!host.equals(i2cpHost) || + (port != _util.getI2CPPort()) || + (!oldOpts.equals(opts)))) ) { + boolean snarksActive = false; + Set names = listTorrentFiles(); + for (Iterator iter = names.iterator(); iter.hasNext(); ) { + Snark snark = getTorrent((String)iter.next()); + if ( (snark != null) && (!snark.stopped) ) { + snarksActive = true; + break; + } + } + if (snarksActive) { + Properties p = new Properties(); + p.putAll(opts); + _util.setI2CPConfig(i2cpHost, port, p); + addMessage(_("I2CP and tunnel changes will take effect after stopping all torrents")); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts + + "] oldOpts [" + oldOpts + "]"); + } else { + if (_util.connected()) { + _util.disconnect(); + addMessage(_("Disconnecting old I2CP destination")); + } + Properties p = new Properties(); + p.putAll(opts); + addMessage(_("I2CP settings changed to {0}", i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")")); + _util.setI2CPConfig(i2cpHost, port, p); + boolean ok = _util.connect(); + if (!ok) { + addMessage(_("Unable to connect with the new settings, reverting to the old I2CP settings")); + _util.setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts); + ok = _util.connect(); + if (!ok) + addMessage(_("Unable to reconnect with the old settings!")); + } else { + addMessage(_("Reconnected on the new I2CP destination")); + _config.setProperty(PROP_I2CP_HOST, i2cpHost.trim()); + _config.setProperty(PROP_I2CP_PORT, "" + port); + _config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim()); + changed = true; + // no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive + for (Iterator iter = names.iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + Snark snark = getTorrent(name); + if ( (snark != null) && (snark.acceptor != null) ) { + snark.acceptor.restart(); + addMessage(_("I2CP listener restarted for \"{0}\"", snark.meta.getName())); + } + } + } + } + changed = true; + } + } + if (shouldAutoStart() != autoStart) { + _config.setProperty(PROP_AUTO_START, autoStart + ""); + if (autoStart) + addMessage(_("Enabled autostart")); + else + addMessage(_("Disabled autostart")); + changed = true; + } + if (_util.shouldUseOpenTrackers() != useOpenTrackers) { + _config.setProperty(I2PSnarkUtil.PROP_USE_OPENTRACKERS, useOpenTrackers + ""); + if (useOpenTrackers) + addMessage(_("Enabled open trackers - torrent restart required to take effect.")); + else + addMessage(_("Disabled open trackers - torrent restart required to take effect.")); + changed = true; + } + if (openTrackers != null) { + if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(_util.getOpenTrackerString())) { + _config.setProperty(I2PSnarkUtil.PROP_OPENTRACKERS, openTrackers.trim()); + _util.setOpenTrackerString(openTrackers); + addMessage(_("Open Tracker list changed - torrent restart required to take effect.")); + changed = true; + } + } + if (changed) { + saveConfig(); + } else { + addMessage(_("Configuration unchanged.")); + } + } + + public void saveConfig() { + try { + synchronized (_configFile) { + DataHelper.storeProps(_config, _configFile); + } + } catch (IOException ioe) { + addMessage(_("Unable to save the config to {0}", _configFile.getAbsolutePath())); + } + } + + public Properties getConfig() { return _config; } + + /** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */ + private static final int MAX_FILES_PER_TORRENT = 512; + + /** set of canonical .torrent filenames that we are dealing with */ + public Set<String> listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } } + + /** + * Grab the torrent given the (canonical) filename of the .torrent file + * @return Snark or null + */ + public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } } + + /** + * Grab the torrent given the base name of the storage + * @return Snark or null + * @since 0.7.14 + */ + public Snark getTorrentByBaseName(String filename) { + synchronized (_snarks) { + for (Snark s : _snarks.values()) { + if (s.storage.getBaseName().equals(filename)) + return s; + } + } + return null; + } + + /** @throws RuntimeException via Snark.fatal() */ + public void addTorrent(String filename) { addTorrent(filename, false); } + + /** @throws RuntimeException via Snark.fatal() */ + public void addTorrent(String filename, boolean dontAutoStart) { + if ((!dontAutoStart) && !_util.connected()) { + addMessage(_("Connecting to I2P")); + boolean ok = _util.connect(); + if (!ok) { + addMessage(_("Error connecting to I2P - check your I2CP settings!")); + return; + } + } + File sfile = new File(filename); + try { + filename = sfile.getCanonicalPath(); + } catch (IOException ioe) { + _log.error("Unable to add the torrent " + filename, ioe); + addMessage(_("Error: Could not add the torrent {0}", filename) + ": " + ioe.getMessage()); + return; + } + File dataDir = getDataDir(); + Snark torrent = null; + synchronized (_snarks) { + torrent = (Snark)_snarks.get(filename); + } + // don't hold the _snarks lock while verifying the torrent + if (torrent == null) { + synchronized (_addSnarkLock) { + // double-check + synchronized (_snarks) { + if(_snarks.get(filename) != null) + return; + } + + FileInputStream fis = null; + try { + fis = new FileInputStream(sfile); + } catch (IOException ioe) { + // catch this here so we don't try do delete it below + addMessage(_("Cannot open \"{0}\"", sfile.getName()) + ": " + ioe.getMessage()); + return; + } + + try { + MetaInfo info = new MetaInfo(fis); + try { + fis.close(); + fis = null; + } catch (IOException e) {} + + if (!TrackerClient.isValidAnnounce(info.getAnnounce())) { + if (_util.shouldUseOpenTrackers() && _util.getOpenTrackers() != null) { + addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only", info.getName())); + } else { + addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!", info.getName())); + dontAutoStart = true; + } + } + String rejectMessage = locked_validateTorrent(info); + if (rejectMessage != null) { + sfile.delete(); + addMessage(rejectMessage); + return; + } else { + torrent = new Snark(_util, filename, null, -1, null, null, this, + _peerCoordinatorSet, _connectionAcceptor, + false, dataDir.getPath()); + torrent.completeListener = this; + synchronized (_snarks) { + _snarks.put(filename, torrent); + } + } + } catch (IOException ioe) { + addMessage(_("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage()); + if (sfile.exists()) + sfile.delete(); + return; + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + } + } else { + return; + } + // ok, snark created, now lets start it up or configure it further + File f = new File(filename); + if (!dontAutoStart && shouldAutoStart()) { + torrent.startTorrent(); + addMessage(_("Torrent added and started: \"{0}\"", f.getName())); + } else { + addMessage(_("Torrent added: \"{0}\"", f.getName())); + } + } + + /** + * Get the timestamp for a torrent from the config file + */ + public long getSavedTorrentTime(Snark snark) { + MetaInfo metainfo = snark.meta; + byte[] ih = metainfo.getInfoHash(); + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); + if (time == null) + return 0; + int comma = time.indexOf(','); + if (comma <= 0) + return 0; + time = time.substring(0, comma); + try { return Long.parseLong(time); } catch (NumberFormatException nfe) {} + return 0; + } + + /** + * Get the saved bitfield for a torrent from the config file. + * Convert "." to a full bitfield. + */ + public BitField getSavedTorrentBitField(Snark snark) { + MetaInfo metainfo = snark.meta; + byte[] ih = metainfo.getInfoHash(); + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); + if (bf == null) + return null; + int comma = bf.indexOf(','); + if (comma <= 0) + return null; + bf = bf.substring(comma + 1).trim(); + int len = metainfo.getPieces(); + if (bf.equals(".")) { + BitField bitfield = new BitField(len); + for (int i = 0; i < len; i++) + bitfield.set(i); + return bitfield; + } + byte[] bitfield = Base64.decode(bf); + if (bitfield == null) + return null; + if (bitfield.length * 8 < len) + return null; + return new BitField(bitfield, len); + } + + /** + * Save the completion status of a torrent and the current time in the config file + * in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield". + * The config file property key is appended with the Base64 of the infohash, + * with the '=' changed to '$' since a key can't contain '='. + * The time is a standard long converted to string. + * The status is either a bitfield converted to Base64 or "." for a completed + * torrent to save space in the config file and in memory. + */ + public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) { + byte[] ih = metainfo.getInfoHash(); + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + String now = "" + System.currentTimeMillis(); + String bfs; + if (bitfield.complete()) { + bfs = "."; + } else { + byte[] bf = bitfield.getFieldBytes(); + bfs = Base64.encode(bf); + } + _config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs); + saveConfig(); + } + + /** + * Remove the status of a torrent from the config file. + * This may help the config file from growing too big. + */ + public void removeTorrentStatus(MetaInfo metainfo) { + byte[] ih = metainfo.getInfoHash(); + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + _config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); + saveConfig(); + } + + /** + * Warning - does not validate announce URL - use TrackerClient.isValidAnnounce() + */ + private String locked_validateTorrent(MetaInfo info) throws IOException { + List files = info.getFiles(); + if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) { + return _("Too many files in \"{0}\" ({1}), deleting it!", info.getName(), files.size()); + } else if ( (files == null) && (info.getName().endsWith(".torrent")) ) { + return _("Torrent file \"{0}\" cannot end in \".torrent\", deleting it!", info.getName()); + } else if (info.getPieces() <= 0) { + return _("No pieces in \"{0}\", deleting it!", info.getName()); + } else if (info.getPieces() > Storage.MAX_PIECES) { + return _("Too many pieces in \"{0}\", limit is {1}, deleting it!", info.getName(), Storage.MAX_PIECES); + } else if (info.getPieceLength(0) > Storage.MAX_PIECE_SIZE) { + return _("Pieces are too large in \"{0}\" ({1}B), deleting it.", info.getName(), DataHelper.formatSize2(info.getPieceLength(0))) + ' ' + + _("Limit is {0}B", DataHelper.formatSize2(Storage.MAX_PIECE_SIZE)); + } else if (info.getTotalLength() > Storage.MAX_TOTAL_SIZE) { + System.out.println("torrent info: " + info.toString()); + List lengths = info.getLengths(); + if (lengths != null) + for (int i = 0; i < lengths.size(); i++) + System.out.println("File " + i + " is " + lengths.get(i) + " long."); + + return _("Torrents larger than {0}B are not supported yet, deleting \"{1}\"", Storage.MAX_TOTAL_SIZE, info.getName()); + } else { + // ok + return null; + } + } + + /** + * Stop the torrent, leaving it on the list of torrents unless told to remove it + */ + public Snark stopTorrent(String filename, boolean shouldRemove) { + File sfile = new File(filename); + try { + filename = sfile.getCanonicalPath(); + } catch (IOException ioe) { + _log.error("Unable to remove the torrent " + filename, ioe); + addMessage(_("Error: Could not remove the torrent {0}", filename) + ": " + ioe.getMessage()); + return null; + } + int remaining = 0; + Snark torrent = null; + synchronized (_snarks) { + if (shouldRemove) + torrent = (Snark)_snarks.remove(filename); + else + torrent = (Snark)_snarks.get(filename); + remaining = _snarks.size(); + } + if (torrent != null) { + boolean wasStopped = torrent.stopped; + torrent.stopTorrent(); + if (remaining == 0) { + // should we disconnect/reconnect here (taking care to deal with the other thread's + // I2PServerSocket.accept() call properly?) + ////_util. + } + if (!wasStopped) + addMessage(_("Torrent stopped: \"{0}\"", sfile.getName())); + } + return torrent; + } + /** + * Stop the torrent and delete the torrent file itself, but leaving the data + * behind. + */ + public void removeTorrent(String filename) { + Snark torrent = stopTorrent(filename, true); + if (torrent != null) { + File torrentFile = new File(filename); + torrentFile.delete(); + if (torrent.storage != null) + removeTorrentStatus(torrent.storage.getMetaInfo()); + addMessage(_("Torrent removed: \"{0}\"", torrentFile.getName())); + } + } + + private class DirMonitor implements Runnable { + public void run() { + try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {} + // the first message was a "We are starting up in 1m" + synchronized (_messages) { + if (_messages.size() == 1) + _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(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Directory Monitor loop over " + dir.getAbsolutePath()); + try { + monitorTorrents(dir); + } catch (Exception e) { + _log.error("Error in the DirectoryMonitor", e); + } + try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} + } + } + } + + /** two listeners */ + public void torrentComplete(Snark snark) { + StringBuilder buf = new StringBuilder(256); + buf.append("<a href=\"/i2psnark/").append(snark.storage.getBaseName()); + if (snark.meta.getFiles() != null) + buf.append('/'); + buf.append("\">").append(snark.storage.getBaseName()).append("</a>"); + long len = snark.meta.getTotalLength(); + addMessage(_("Download finished: {0}", buf.toString()) + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')'); + updateStatus(snark); + } + + public void updateStatus(Snark snark) { + saveTorrentStatus(snark.meta, snark.storage.getBitField()); + } + + private void monitorTorrents(File dir) { + String fileNames[] = dir.list(TorrentFilenameFilter.instance()); + List<String> foundNames = new ArrayList(0); + if (fileNames != null) { + for (int i = 0; i < fileNames.length; i++) { + try { + foundNames.add(new File(dir, fileNames[i]).getCanonicalPath()); + } catch (IOException ioe) { + _log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe); + } + } + } + + Set<String> existingNames = listTorrentFiles(); + // lets find new ones first... + for (int i = 0; i < foundNames.size(); i++) { + if (existingNames.contains(foundNames.get(i))) { + // already known. noop + } else { + if (shouldAutoStart() && !_util.connect()) + addMessage(_("Unable to connect to I2P!")); + try { + // Snark.fatal() throws a RuntimeException + // don't let one bad torrent kill the whole loop + addTorrent(foundNames.get(i), !shouldAutoStart()); + } catch (Exception e) { + addMessage(_("Unable to add {0}", foundNames.get(i)) + ": " + e); + } + } + } + // now lets see which ones have been removed... + for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + if (foundNames.contains(name)) { + // known and still there. noop + } else { + // known, but removed. drop it + try { + // Snark.fatal() throws a RuntimeException + // don't let one bad torrent kill the whole loop + stopTorrent(name, true); + } catch (Exception e) { + // don't bother with message + } + } + } + } + + /** translate */ + private String _(String s) { + return _util.getString(s); + } + + /** translate */ + private String _(String s, Object o) { + return _util.getString(s, o); + } + + /** translate */ + private String _(String s, Object o, Object o2) { + return _util.getString(s, o, o2); + } + + /** + * "name", "announceURL=websiteURL" pairs + */ + private static final String DEFAULT_TRACKERS[] = { +// "Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/" +// , "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/" +// , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/" +// , "NickyB", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php=http://nickyb.i2p/bittorrent/" +// , "Orion", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php=http://orion.i2p/bt/" +// , "anonymity", "http://8EoJZIKrWgGuDrxA3nRJs1jsPfiGwmFWL91hBrf0HA7oKhEvAna4Ocx47VLUR9retVEYBAyWFK-eZTPcvhnz9XffBEiJQQ~kFSCqb1fV6IfPiV3HySqi9U5Caf6~hC46fRd~vYnxmaBLICT3N160cxBETqH3v2rdxdJpvYt8q4nMk9LUeVXq7zqCTFLLG5ig1uKgNzBGe58iNcsvTEYlnbYcE930ABmrzj8G1qQSgSwJ6wx3tUQNl1z~4wSOUMan~raZQD60lRK70GISjoX0-D0Po9WmPveN3ES3g72TIET3zc3WPdK2~lgmKGIs8GgNLES1cXTolvbPhdZK1gxddRMbJl6Y6IPFyQ9o4-6Rt3Lp-RMRWZ2TG7j2OMcNSiOmATUhKEFBDfv-~SODDyopGBmfeLw16F4NnYednvn4qP10dyMHcUASU6Zag4mfc2-WivrOqeWhD16fVAh8MoDpIIT~0r9XmwdaVFyLcjbXObabJczxCAW3fodQUnvuSkwzAAAA.i2p/anonymityTracker/announce.php=http://anonymityweb.i2p/anonymityTracker/" +// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php" +// , "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/" +// , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/" + "POSTMAN", "http://tracker2.postman.i2p/announce.php=http://tracker2.postman.i2p/" + ,"WELTERDE", "http://tracker.welterde.i2p/a=http://tracker.welterde.i2p/stats?mode=top5" + , "CRSTRACK", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/" + }; + + /** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */ + public static final String PROP_TRACKERS = "i2psnark.trackers"; + private static Map trackerMap = null; + /** sorted map of name to announceURL=baseURL */ + public Map getTrackers() { + if (trackerMap != null) // only do this once, can't be updated while running + return trackerMap; + Map rv = new TreeMap(); + String trackers = _config.getProperty(PROP_TRACKERS); + if ( (trackers == null) || (trackers.trim().length() <= 0) ) + trackers = _context.getProperty(PROP_TRACKERS); + if ( (trackers == null) || (trackers.trim().length() <= 0) ) { + for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2) + rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]); + } else { + StringTokenizer tok = new StringTokenizer(trackers, ","); + while (tok.hasMoreTokens()) { + String pair = tok.nextToken(); + int split = pair.indexOf('='); + if (split <= 0) + continue; + String name = pair.substring(0, split).trim(); + String url = pair.substring(split+1).trim(); + if ( (name.length() > 0) && (url.length() > 0) ) + rv.put(name, url); + } + } + + trackerMap = rv; + return trackerMap; + } + + private static class TorrentFilenameFilter implements FilenameFilter { + private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter(); + public static TorrentFilenameFilter instance() { return _filter; } + public boolean accept(File dir, String name) { + return (name != null) && (name.endsWith(".torrent")); + } + } + + public class SnarkManagerShutdown extends I2PAppThread { + @Override + public void run() { + Set names = listTorrentFiles(); + for (Iterator iter = names.iterator(); iter.hasNext(); ) { + Snark snark = getTorrent((String)iter.next()); + if ( (snark != null) && (!snark.stopped) ) + snark.stopTorrent(); + } + } + } +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index 065e7088014a7a1b19c489e75ab31fe7ee230528..05df351c69b7c28e93deff8e783e1aa0a537cf21 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -357,6 +357,7 @@ public class TrackerClient extends I2PAppThread + "&uploaded=" + uploaded + "&downloaded=" + downloaded + "&left=" + left + + "&compact" + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : ""); if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) s += "&numwant=0"; diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java index 84198f12f0f0eacca955c53cf14a018c91202f1e..360a4f47e416c3ed728523cfcfbd4e5a09ca9b55 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,11 +32,17 @@ import org.klomp.snark.bencode.BDecoder; import org.klomp.snark.bencode.BEValue; import org.klomp.snark.bencode.InvalidBEncodingException; +/** + * The data structure for the tracker response. + * Handles both traditional and compact formats. + * Compact format 1 - a list of hashes - early format for testing + * Compact format 2 - One big string of concatenated hashes - official format + */ public class TrackerInfo { private final String failure_reason; private final int interval; - private final Set peers; + private final Set<Peer> peers; private int complete; private int incomplete; @@ -73,10 +78,19 @@ public class TrackerInfo interval = beInterval.getInt(); BEValue bePeers = (BEValue)m.get("peers"); - if (bePeers == null) + if (bePeers == null) { peers = Collections.EMPTY_SET; - else - peers = getPeers(bePeers.getList(), my_id, metainfo); + } else { + Set<Peer> p; + try { + // One big string (the official compact format) + p = getPeers(bePeers.getBytes(), my_id, metainfo); + } catch (InvalidBEncodingException ibe) { + // List of Dictionaries or List of Strings + p = getPeers(bePeers.getList(), my_id, metainfo); + } + peers = p; + } BEValue bev = (BEValue)m.get("complete"); if (bev != null) try { @@ -94,32 +108,68 @@ public class TrackerInfo } } - public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo) +/****** + public static Set<Peer> getPeers(InputStream in, byte[] my_id, MetaInfo metainfo) throws IOException { return getPeers(new BDecoder(in), my_id, metainfo); } - public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo) + public static Set<Peer> getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo) throws IOException { return getPeers(be.bdecodeList().getList(), my_id, metainfo); } +******/ - public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo) + /** List of Dictionaries or List of Strings */ + private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo) throws IOException { - Set peers = new HashSet(l.size()); + Set<Peer> peers = new HashSet(l.size()); - Iterator it = l.iterator(); - while (it.hasNext()) - { + for (BEValue bev : l) { + PeerID peerID; + try { + // Case 1 - non-compact - A list of dictionaries (maps) + peerID = new PeerID(bev.getMap()); + } catch (InvalidBEncodingException ibe) { + try { + // Case 2 - compact - A list of 32-byte binary strings (hashes) + // This was just for testing and is not the official format + peerID = new PeerID(bev.getBytes()); + } catch (InvalidBEncodingException ibe2) { + // don't let one bad entry spoil the whole list + //Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR); + continue; + } + } + peers.add(new Peer(peerID, my_id, metainfo)); + } + + return peers; + } + + private static final int HASH_LENGTH = 32; + + /** + * One big string of concatenated 32-byte hashes + * @since 0.8.1 + */ + private static Set<Peer> getPeers(byte[] l, byte[] my_id, MetaInfo metainfo) + throws IOException + { + int count = l.length / HASH_LENGTH; + Set<Peer> peers = new HashSet(count); + + for (int i = 0; i < count; i++) { PeerID peerID; + byte[] hash = new byte[HASH_LENGTH]; + System.arraycopy(l, i * HASH_LENGTH, hash, 0, HASH_LENGTH); try { - peerID = new PeerID(((BEValue)it.next()).getMap()); + peerID = new PeerID(hash); } catch (InvalidBEncodingException ibe) { - // don't let one bad entry spoil the whole list - //Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR); + // won't happen continue; } peers.add(new Peer(peerID, my_id, metainfo)); @@ -128,7 +178,7 @@ public class TrackerInfo return peers; } - public Set getPeers() + public Set<Peer> getPeers() { return peers; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java index cba6b973fd2d70f846e32dd690228cdcb6996e03..c1733cb13b2000ddcc62e0fa29cb82a5fcd6bd60 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java +++ b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java @@ -140,7 +140,7 @@ public class BEValue * succeeds when the BEValue is actually a List, otherwise it will * throw a InvalidBEncodingException. */ - public List getList() throws InvalidBEncodingException + public List<BEValue> getList() throws InvalidBEncodingException { try { @@ -157,7 +157,7 @@ public class BEValue * values. This operation only succeeds when the BEValue is actually * a Map, otherwise it will throw a InvalidBEncodingException. */ - public Map getMap() throws InvalidBEncodingException + public Map<BEValue, BEValue> getMap() throws InvalidBEncodingException { try { diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 9a291c89f9cc074cfc8461c30924ff9625c22bd5..36266de1be716d90842858e2effab990d5eca4fb 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -8,6 +8,7 @@ import java.io.PrintWriter; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; @@ -821,10 +822,10 @@ public class I2PSnarkServlet extends Default { out.write("</td>\n</tr>\n"); if(showPeers && isRunning && curPeers > 0) { - List peers = snark.coordinator.peerList(); - Iterator it = peers.iterator(); - while (it.hasNext()) { - Peer peer = (Peer)it.next(); + List<Peer> peers = snark.coordinator.peerList(); + if (!showDebug) + Collections.sort(peers, new PeerComparator()); + for (Peer peer : peers) { if (!peer.isConnected()) continue; out.write("<tr class=\"" + rowClass + "\">"); @@ -908,6 +909,19 @@ public class I2PSnarkServlet extends Default { } } + /** + * Sort by completeness (seeds first), then by ID + * @since 0.8.1 + */ + private static class PeerComparator implements Comparator<Peer> { + public int compare(Peer l, Peer r) { + int diff = r.completed() - l.completed(); // reverse + if (diff != 0) + return diff; + return l.toString().substring(5, 9).compareTo(r.toString().substring(5, 9)); + } + } + private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException { String uri = req.getRequestURI(); String newURL = req.getParameter("newURL"); diff --git a/apps/i2psnark/web.xml b/apps/i2psnark/web.xml index 71260245f1d5fd295306f09c5779a7bfe98b8026..2925ff00e3267ce2a2539db7ca0e61ba489a1b46 100644 --- a/apps/i2psnark/web.xml +++ b/apps/i2psnark/web.xml @@ -22,4 +22,68 @@ 30 </session-timeout> </session-config> + + + <!-- mime types not in mime.properties in the jetty 5.1.15 source --> + + <mime-mapping> + <extension>mkv</extension> + <mime-type>video/x-matroska</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>wmv</extension> + <mime-type>video/x-ms-wmv</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>flv</extension> + <mime-type>video/x-flv</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>mp4</extension> + <mime-type>video/mp4</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>rar</extension> + <mime-type>application/rar</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>7z</extension> + <mime-type>application/x-7z-compressed</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>iso</extension> + <mime-type>application/x-iso9660-image</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>ico</extension> + <mime-type>image/x-icon</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>exe</extension> + <mime-type>application/x-msdos-program</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>flac</extension> + <mime-type>audio/flac</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>m4a</extension> + <mime-type>audio/mpeg</mime-type> + </mime-mapping> + + <mime-mapping> + <extension>wma</extension> + <mime-type>audio/x-ms-wma</mime-type> + </mime-mapping> + </web-app> diff --git a/apps/i2ptunnel/java/build.xml b/apps/i2ptunnel/java/build.xml index 7b66b6882e624a4f44ca3b04aaf9763f20d378f1..b17e8d9f38901bc3c90ff87546b2eaa352e49bf7 100644 --- a/apps/i2ptunnel/java/build.xml +++ b/apps/i2ptunnel/java/build.xml @@ -81,7 +81,7 @@ <target name="war" depends="precompilejsp, bundle"> <war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml" - basedir="../jsp/" excludes="web.xml, **/*.java, *.jsp"> + basedir="../jsp/" excludes="web.xml, web-fragment.xml, web-out.xml, **/*.java, *.jsp"> </war> </target> diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java index af4049a21b09183a70f2e84f4d588873e937535a..326ebb644172633fc2a8bcee2bcbbfb1d40f8db9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java @@ -24,6 +24,9 @@ import net.i2p.util.I2PAppThread; import net.i2p.util.Log; /** + * This does the transparent gzip decompression on the client side. + * Extended in I2PTunnelHTTPServer to do the compression on the server side. + * * Simple stream for delivering an HTTP response to * the client, trivially filtered to make sure "Connection: close" * is always in the response. Perhaps add transparent handling of the @@ -33,29 +36,27 @@ import net.i2p.util.Log; * */ class HTTPResponseOutputStream extends FilterOutputStream { - private I2PAppContext _context; - private Log _log; - private ByteCache _cache; + private final I2PAppContext _context; + private final Log _log; protected ByteArray _headerBuffer; private boolean _headerWritten; - private byte _buf1[]; + private final byte _buf1[]; protected boolean _gzip; private long _dataWritten; private InternalGZIPInputStream _in; private static final int CACHE_SIZE = 8*1024; + private static final ByteCache _cache = ByteCache.getInstance(8, CACHE_SIZE); + // OOM DOS prevention + private static final int MAX_HEADER_SIZE = 64*1024; public HTTPResponseOutputStream(OutputStream raw) { super(raw); _context = I2PAppContext.getGlobalContext(); - _context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "I2PTunnel", new long[] { 60*1000, 30*60*1000 }); - _context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*1000, 30*60*1000 }); - _context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*1000, 30*60*1000 }); + _context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "I2PTunnel", new long[] { 60*60*1000 }); + _context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*60*1000 }); + _context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*60*1000 }); _log = _context.logManager().getLog(getClass()); - _cache = ByteCache.getInstance(8, CACHE_SIZE); _headerBuffer = _cache.acquire(); - _headerWritten = false; - _gzip = false; - _dataWritten = 0; _buf1 = new byte[1]; } @@ -96,14 +97,20 @@ class HTTPResponseOutputStream extends FilterOutputStream { } } - /** grow (and free) the buffer as necessary */ - private void ensureCapacity() { + /** + * grow (and free) the buffer as necessary + * @throws IOException if the headers are too big + */ + private void ensureCapacity() throws IOException { + if (_headerBuffer.getValid() >= MAX_HEADER_SIZE) + throw new IOException("Max header size exceeded: " + MAX_HEADER_SIZE); if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) { int newSize = (int)(_headerBuffer.getData().length * 1.5); ByteArray newBuf = new ByteArray(new byte[newSize]); System.arraycopy(_headerBuffer.getData(), 0, newBuf.getData(), 0, _headerBuffer.getValid()); newBuf.setValid(_headerBuffer.getValid()); newBuf.setOffset(0); + // if we changed the ByteArray size, don't put it back in the cache if (_headerBuffer.getData().length == CACHE_SIZE) _cache.release(_headerBuffer); _headerBuffer = newBuf; @@ -219,7 +226,7 @@ class HTTPResponseOutputStream extends FilterOutputStream { //out.flush(); PipedInputStream pi = new PipedInputStream(); PipedOutputStream po = new PipedOutputStream(pi); - new I2PAppThread(new Pusher(pi, out), "HTTP decompresser").start(); + new I2PAppThread(new Pusher(pi, out), "HTTP decompressor").start(); out = po; } @@ -231,13 +238,13 @@ class HTTPResponseOutputStream extends FilterOutputStream { _out = out; } public void run() { - OutputStream to = null; _in = null; - long start = System.currentTimeMillis(); long written = 0; + ByteArray ba = null; try { _in = new InternalGZIPInputStream(_inRaw); - byte buf[] = new byte[8192]; + ba = _cache.acquire(); + byte buf[] = ba.getData(); int read = -1; while ( (read = _in.read(buf)) != -1) { if (_log.shouldLog(Log.DEBUG)) @@ -251,6 +258,8 @@ class HTTPResponseOutputStream extends FilterOutputStream { } catch (IOException ioe) { if (_log.shouldLog(Log.WARN)) _log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe); + } catch (OutOfMemoryError oom) { + _log.error("OOM in HTTP Decompressor", oom); } finally { if (_log.shouldLog(Log.WARN) && (_in != null)) _log.warn("After decompression, written=" + written + @@ -259,23 +268,26 @@ class HTTPResponseOutputStream extends FilterOutputStream { + ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining() + ", finished=" + _in.getFinished() : "")); + if (ba != null) + _cache.release(ba); if (_out != null) try { _out.close(); } catch (IOException ioe) {} } - long end = System.currentTimeMillis(); + double compressed = (_in != null ? _in.getTotalRead() : 0); double expanded = (_in != null ? _in.getTotalExpanded() : 0); - double ratio = 0; - if (expanded > 0) - ratio = compressed/expanded; - - _context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start); - _context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start); - _context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start); + if (compressed > 0 && expanded > 0) { + // only update the stats if we did something + double ratio = compressed/expanded; + _context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), 0); + _context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, 0); + _context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, 0); + } } } + /** just a wrapper to provide stats for debugging */ private static class InternalGZIPInputStream extends GZIPInputStream { public InternalGZIPInputStream(InputStream in) throws IOException { super(in); @@ -294,6 +306,12 @@ class HTTPResponseOutputStream extends FilterOutputStream { return 0; } } + + /** + * From Inflater javadoc: + * Returns the total number of bytes remaining in the input buffer. This can be used to find out + * what bytes still remain in the input buffer after decompression has finished. + */ public long getRemaining() { try { return super.inf.getRemaining(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java index d5b88d4d3df1341ea6a0868b5111175ac30285ea..435b309479c7b5da628d1a7944bd3038376ab8cc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; -import net.i2p.I2PAppContext; import net.i2p.client.streaming.I2PSocket; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; @@ -110,7 +109,7 @@ public class I2PTunnelClient extends I2PTunnelClientBase { } if (size == 1) // skip the rand in the most common case return dests.get(0); - int index = I2PAppContext.getGlobalContext().random().nextInt(size); + int index = _context.random().nextInt(size); return dests.get(index); } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java index fe92e94b6f443ab39032770ea705152f733aa6a7..d9afdc5f21b732301ffc92d3a3ab07889b6b44f9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java @@ -146,7 +146,7 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna int size = _proxyList.size(); if (size <= 0) return null; - int index = I2PAppContext.getGlobalContext().random().nextInt(size); + int index = _context.random().nextInt(size); return _proxyList.get(index); } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index b6c72821e5327fdca304fae27f6e4798a9370b57..f34ceb1a59f929130c728669a18e55531c851c81 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -205,7 +205,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable l.log("Proxy list is empty - no outproxy available"); return null; } - int index = I2PAppContext.getGlobalContext().random().nextInt(size); + int index = _context.random().nextInt(size); String proxy = (String)proxyList.get(index); return proxy; } @@ -626,6 +626,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable //line = "From: i2p"; line = null; continue; // completely strip the line + } else if (lowercaseLine.startsWith("authorization: ntlm ")) { + // Block Windows NTLM after 401 + line = null; + continue; + } else if (lowercaseLine.startsWith("proxy-authorization: ntlm ")) { + // Block Windows NTLM after 407 + line = null; + continue; } } @@ -808,7 +816,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable * @return non-null */ private byte[] getErrorPage(String base, byte[] backup) { - return getErrorPage(getTunnel().getContext(), base, backup); + return getErrorPage(_context, base, backup); } private static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 728d3537c62ba284a99bccd0e4048a591c9224aa..ca568ab9260e2838fc1dc8326d1c606ecc68a5e8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -290,6 +290,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { } } + /** just a wrapper to provide stats for debugging */ private static class InternalGZIPOutputStream extends GZIPOutputStream { public InternalGZIPOutputStream(OutputStream target) throws IOException { super(target); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java index 4d8b85a8177e002b8356a71448f1eed7c7cf6274..d0dc227ec1da09fde27392cfd4caab52ac4ee0a0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java @@ -9,7 +9,6 @@ import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; -import net.i2p.I2PAppContext; import net.i2p.client.streaming.I2PSocket; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; @@ -124,7 +123,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable } if (size == 1) // skip the rand in the most common case return dests.get(0); - int index = I2PAppContext.getGlobalContext().random().nextInt(size); + int index = _context.random().nextInt(size); return dests.get(index); } @@ -182,6 +181,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable } outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3 output.write(outmsg.getBytes("ISO-8859-1")); + // probably doesn't do much but can't hurt + output.flush(); } else { if (_log.shouldLog(Log.WARN)) _log.warn("inbound BLOCKED: "+inmsg); @@ -257,6 +258,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable } outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3 output.write(outmsg.getBytes("ISO-8859-1")); + // save 250 ms in streaming + output.flush(); } else { if (_log.shouldLog(Log.WARN)) _log.warn("outbound BLOCKED: "+"\""+inmsg+"\""); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java index c2a014020d2df2c74d34c1422bf0d9249afb19fd..0e1c9049f84b8ea5a9c57f314a16bf7b5b150176 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java @@ -129,7 +129,14 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr // do NOT flush here, it will block and then onTimeout.run() won't happen on fail. // But if we don't flush, then we have to wait for the connectDelay timer to fire // in i2p socket? To be researched and/or fixed. - //i2pout.flush(); + // + // AS OF 0.8.1, MessageOutputStream.flush() is fixed to only wait for accept, + // not for "completion" (i.e. an ACK from the far end). + // So we now get a fast return from flush(), and can do it here to save 250 ms. + // To make sure we are under the initial window size and don't hang waiting for accept, + // only flush if it fits in one message. + if (initialI2PData.length <= 1730) // ConnectionOptions.DEFAULT_MAX_MESSAGE_SIZE + i2pout.flush(); } } if (initialSocketData != null) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index f0470c5a30e8a9e643d758cd48e435ddd7350634..02777dafbddd625a9876d83cff1a3ce65f8c8b75 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -18,6 +18,7 @@ import net.i2p.data.Base32; import net.i2p.data.Destination; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; /** * Coordinate the runtime operation and configuration of a tunnel. @@ -89,7 +90,7 @@ public class TunnelController implements Logging { } FileOutputStream fos = null; try { - fos = new FileOutputStream(keyFile); + fos = new SecureFileOutputStream(keyFile); Destination dest = client.createDestination(fos); String destStr = dest.toBase64(); log("Private key created and saved in " + keyFile.getAbsolutePath()); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java index 169f56954eb25d9cecef84dd532cf5ae84e7a725..618b035f7b68cf8bb7838cbb67f897d116c902d8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java @@ -20,6 +20,7 @@ import net.i2p.client.I2PSessionException; import net.i2p.data.DataHelper; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; /** * Coordinate a set of tunnels within the JVM, loading and storing their config @@ -255,7 +256,7 @@ public class TunnelControllerGroup { FileOutputStream fos = null; try { - fos = new FileOutputStream(cfgFile); + fos = new SecureFileOutputStream(cfgFile); fos.write(buf.toString().getBytes("UTF-8")); if (_log.shouldLog(Log.INFO)) _log.info("Config written to " + cfgFile.getPath()); 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 38bc8b4db9483ab53ac74b0df41166e6d50fbb3c..f6c76d7f6066a88b08a181ffe21936032feb939b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -13,6 +13,11 @@ import java.util.List; import java.util.Properties; import java.util.StringTokenizer; +import net.i2p.data.Base64; +import net.i2p.data.Destination; +import net.i2p.data.PrivateKeyFile; +import net.i2p.data.Signature; +import net.i2p.data.SigningPrivateKey; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; @@ -68,6 +73,31 @@ public class EditBean extends IndexBean { return "i2ptunnel" + tunnel + "-privKeys.dat"; } + public String getNameSignature(int tunnel) { + String spoof = getSpoofedHost(tunnel); + if (spoof.length() <= 0) + return ""; + TunnelController tun = getController(tunnel); + if (tun == null) + return ""; + String keyFile = tun.getPrivKeyFile(); + if (keyFile != null && keyFile.trim().length() > 0) { + PrivateKeyFile pkf = new PrivateKeyFile(keyFile); + try { + Destination d = pkf.getDestination(); + if (d == null) + return ""; + SigningPrivateKey privKey = pkf.getSigningPrivKey(); + if (privKey == null) + return ""; + //System.err.println("Signing " + spoof + " with " + Base64.encode(privKey.getData())); + Signature sig = _context.dsa().sign(spoof.getBytes("UTF-8"), privKey); + return Base64.encode(sig.getData()); + } catch (Exception e) {} + } + return ""; + } + public boolean startAutomatically(int tunnel) { TunnelController tun = getController(tunnel); if (tun != null) 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 2a77b9be8ad3825bd1a73ae6fd52ed7137eb1cf8..d91a7900d793d29245a5e66ab0d676eadf079203 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -9,6 +9,7 @@ package net.i2p.i2ptunnel.web; */ import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -41,10 +42,10 @@ public class IndexBean { protected TunnelControllerGroup _group; private String _action; private int _tunnel; - private long _prevNonce; - private long _prevNonce2; - private long _curNonce; - private long _nextNonce; + //private long _prevNonce; + //private long _prevNonce2; + private String _curNonce; + //private long _nextNonce; private String _type; private String _name; @@ -85,10 +86,14 @@ public class IndexBean { /** deprecated unimplemented, now using routerconsole realm */ //public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase"; public static final String PROP_TUNNEL_PASSPHRASE = "consolePassword"; - static final String PROP_NONCE = IndexBean.class.getName() + ".nonce"; - static final String PROP_NONCE_OLD = PROP_NONCE + '2'; + //static final String PROP_NONCE = IndexBean.class.getName() + ".nonce"; + //static final String PROP_NONCE_OLD = PROP_NONCE + '2'; + /** 3 wasn't enough for some browsers. They are reloading the page for some reason - maybe HEAD? @since 0.8.1 */ + private static final int MAX_NONCES = 5; + /** store nonces in a static FIFO instead of in System Properties @since 0.8.1 */ + private static final List<String> _nonces = new ArrayList(MAX_NONCES + 1); + static final String CLIENT_NICKNAME = "shared clients"; - public static final String PROP_THEME_NAME = "routerconsole.theme"; public static final String DEFAULT_THEME = "light"; public static final String PROP_CSS_DISABLED = "routerconsole.css.disabled"; @@ -98,34 +103,39 @@ public class IndexBean { _context = I2PAppContext.getGlobalContext(); _log = _context.logManager().getLog(IndexBean.class); _group = TunnelControllerGroup.getInstance(); - _action = null; _tunnel = -1; - _curNonce = -1; - _prevNonce = -1; - _prevNonce2 = -1; - try { - String nonce2 = System.getProperty(PROP_NONCE_OLD); - if (nonce2 != null) - _prevNonce2 = Long.parseLong(nonce2); - String nonce = System.getProperty(PROP_NONCE); - if (nonce != null) { - _prevNonce = Long.parseLong(nonce); - System.setProperty(PROP_NONCE_OLD, nonce); - } - } catch (NumberFormatException nfe) {} - _nextNonce = _context.random().nextLong(); - System.setProperty(PROP_NONCE, Long.toString(_nextNonce)); + _curNonce = "-1"; + addNonce(); _booleanOptions = new ConcurrentHashSet(4); _otherOptions = new ConcurrentHashMap(4); } - public long getNextNonce() { return _nextNonce; } + public static String getNextNonce() { + synchronized (_nonces) { + return _nonces.get(0); + } + } + public void setNonce(String nonce) { if ( (nonce == null) || (nonce.trim().length() <= 0) ) return; - try { - _curNonce = Long.parseLong(nonce); - } catch (NumberFormatException nfe) { - _curNonce = -1; + _curNonce = nonce; + } + + /** add a random nonce to the head of the queue @since 0.8.1 */ + private void addNonce() { + String nextNonce = Long.toString(_context.random().nextLong()); + synchronized (_nonces) { + _nonces.add(0, nextNonce); + int sz = _nonces.size(); + if (sz > MAX_NONCES) + _nonces.remove(sz - 1); + } + } + + /** do we know this nonce? @since 0.8.1 */ + private static boolean haveNonce(String nonce) { + synchronized (_nonces) { + return _nonces.contains(nonce); } } @@ -155,7 +165,7 @@ public class IndexBean { private String processAction() { if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action))) return ""; - if ( (_prevNonce != _curNonce) && (_prevNonce2 != _curNonce) && (!validPassphrase()) ) + if ( (!haveNonce(_curNonce)) && (!validPassphrase()) ) return "Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit."; if ("Stop all".equals(_action)) return stopAll(); diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 939ccf52079d490516b75e2215a484df20459b64..97a0a93c9c7eead98b070b8ccbfb2d57a6a57ac4 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -196,7 +196,16 @@ <a href="/susidns/addressbook.jsp?book=private&hostname=<%=editBean.getTunnelName(curTunnel)%>&destination=<%=editBean.getDestinationBase64(curTunnel)%>#add"><%=intl._("Add to local addressbook")%></a> <% } %> </div> - + + <% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { + %><div id="sigField" class="rowItem"> + <label for="signature"> + <%=intl._("Hostname Signature")%> + </label> + <input type="text" size="30" readonly="readonly" title="Use to prove that the website name is for this destination" value="<%=editBean.getNameSignature(curTunnel)%>" wrap="off" class="freetext" /> + </div> + <% } %> + <div class="footer"> </div> </div> diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java index 393cf1ae216690d37b9903b1772607fb2ec7a902..34dc8ac593d0d1c33c9202d71a1eb0f7a354b5e8 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java @@ -130,16 +130,19 @@ public class I2PSocketManagerFactory { if (!opts.containsKey(name)) opts.setProperty(name, System.getProperty(name)); } - boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER)); - if (oldLib && false) { + //boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER)); + //if (oldLib && false) { // for the old streaming lib - opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED); + // opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED); //opts.setProperty("tunnels.depthInbound", "0"); - } else { + //} else { // for new streaming lib: - opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT); + //opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT); + // as of 0.8.1 (I2CP default is BestEffort) + if (!opts.containsKey(I2PClient.PROP_RELIABILITY)) + opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); //p.setProperty("tunnels.depthInbound", "0"); - } + //} if (i2cpHost != null) opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost); diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml index 78384113f9d8023b5ab9b7e72bfce5e6f799ecd4..ae73734cd564f674899d42c9f141ad82ccfe4d72 100644 --- a/apps/routerconsole/java/build.xml +++ b/apps/routerconsole/java/build.xml @@ -113,7 +113,7 @@ <target name="war" depends="precompilejsp"> <!-- Don't include the css in the war, the main build.xml will copy it to docs/themes/console/ --> <war destfile="build/routerconsole.war" webxml="../jsp/web-out.xml" - basedir="../jsp/" excludes="web.xml, *.css, **/*.java, *.jsp, *.jsi, web-fragment.xml"> + basedir="../jsp/" excludes="web.xml, *.css, **/*.java, *.jsp, *.jsi, web-fragment.xml, web-out.xml"> </war> </target> <target name="precompilejsp" unless="precompilejsp.uptodate"> diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHandler.java index 347cfdab7f654cfe198d150d2d6fa3b273b4b34d..69013f60a469e9e2d2ec367ce1c1f0f5502e478d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHandler.java @@ -17,6 +17,8 @@ public class ConfigLoggingHandler extends FormHandler { private String _recordFormat; private String _dateFormat; private String _fileSize; + private String _newLogClass; + private String _newLogLevel = "WARN"; @Override protected void processForm() { @@ -26,7 +28,7 @@ public class ConfigLoggingHandler extends FormHandler { // noop } } - + public void setShouldsave(String moo) { _shouldSave = true; } public void setLevels(String levels) { @@ -47,6 +49,18 @@ public class ConfigLoggingHandler extends FormHandler { public void setLogfilesize(String size) { _fileSize = (size != null ? size.trim() : null); } + + /** @since 0.8.1 */ + public void setNewlogclass(String s) { + if (s != null && s.length() > 0) + _newLogClass = s; + } + + /** @since 0.8.1 */ + public void setNewloglevel(String s) { + if (s != null) + _newLogLevel = s; + } /** * The user made changes to the config and wants to save them, so @@ -56,14 +70,18 @@ public class ConfigLoggingHandler extends FormHandler { private void saveChanges() { boolean shouldSave = false; - if (_levels != null) { + if (_levels != null || _newLogClass != null) { try { Properties props = new Properties(); - props.load(new ByteArrayInputStream(_levels.getBytes())); + if (_levels != null) + props.load(new ByteArrayInputStream(_levels.getBytes())); + if (_newLogClass != null) + props.setProperty(_newLogClass, _newLogLevel); _context.logManager().setLimits(props); shouldSave = true; - addFormNotice("Log limits updated"); + addFormNotice(_("Log overrides updated")); } catch (IOException ioe) { + // shouldn't ever happen (BAIS shouldnt cause an IOE) _context.logManager().getLog(ConfigLoggingHandler.class).error("Error reading from the props?", ioe); addFormError("Error updating the log limits - levels not valid"); } @@ -83,7 +101,7 @@ public class ConfigLoggingHandler extends FormHandler { } } - if (_dateFormat != null) { + if (_dateFormat != null && !_dateFormat.equals(_context.logManager().getDateFormatPattern())) { boolean valid = _context.logManager().setDateFormat(_dateFormat); if (valid) { shouldSave = true; @@ -139,7 +157,7 @@ public class ConfigLoggingHandler extends FormHandler { boolean saved = _context.logManager().saveConfig(); if (saved) - addFormNotice("Log configuration saved and applied successfully"); + addFormNotice(_("Log configuration saved")); else addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs"); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java index bde9b08930d2a5b83014a2053b244acd7a30f693..ce41b1e12e189f987ac9f2ce6b8a7c97cd2892e7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java @@ -1,9 +1,13 @@ package net.i2p.router.web; +import java.util.List; import java.util.Iterator; import java.util.Properties; +import java.util.Set; import java.util.TreeSet; +import net.i2p.data.DataHelper; +import net.i2p.util.Log; public class ConfigLoggingHelper extends HelperBase { public ConfigLoggingHelper() {} @@ -19,18 +23,13 @@ public class ConfigLoggingHelper extends HelperBase { } public String getMaxFileSize() { int bytes = _context.logManager().getFileSize(); - if (bytes == 0) return "1m"; - if (bytes > 1024*1024*1024) - return (bytes/(1024*1024*1024)) + "g"; - else if (bytes > 1024*1024) - return (bytes/(1024*1024)) + "m"; - else - return (bytes/(1024)) + "k"; + if (bytes <= 0) return "1.00 MB"; + return DataHelper.formatSize2(bytes) + 'B'; } public String getLogLevelTable() { StringBuilder buf = new StringBuilder(32*1024); Properties limits = _context.logManager().getLimits(); - TreeSet sortedLogs = new TreeSet(); + TreeSet<String> sortedLogs = new TreeSet(); for (Iterator iter = limits.keySet().iterator(); iter.hasNext(); ) { String prefix = (String)iter.next(); sortedLogs.add(prefix); @@ -46,6 +45,20 @@ public class ConfigLoggingHelper extends HelperBase { buf.append("<i>" + _("Add additional logging statements above. Example: net.i2p.router.tunnel=WARN") + "</i><br>"); buf.append("<i>" + _("Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN") + "</i><br>"); buf.append("<i>" + _("Valid levels are DEBUG, INFO, WARN, ERROR, CRIT") + "</i>\n"); + + /**** + // this is too big and ugly + if (limits.size() <= 0) + return ""; + buf.append("<table>"); + for (String prefix : sortedLogs) { + buf.append("<tr><td>").append(prefix).append("</td><td>"); + String level = limits.getProperty(prefix); + buf.append(getLogLevelBox("level-" + prefix, level, true)).append("</td></tr>"); + } + buf.append("</table>"); + ****/ + return buf.toString(); } @@ -53,18 +66,69 @@ public class ConfigLoggingHelper extends HelperBase { public String getDefaultLogLevelBox() { String cur = _context.logManager().getDefaultLimit(); + return getLogLevelBox("defaultloglevel", cur, false); + } + + private String getLogLevelBox(String name, String cur, boolean showRemove) { StringBuilder buf = new StringBuilder(128); - buf.append("<select name=\"defaultloglevel\">\n"); + buf.append("<select name=\"").append(name).append("\">\n"); for (int i = 0; i < levels.length; i++) { String l = levels[i]; - buf.append("<option value=\"").append(l).append("\" "); + buf.append("<option value=\"").append(l).append('\"'); if (l.equals(cur)) - buf.append(" selected=\"true\" "); + buf.append(" selected=\"true\""); buf.append('>').append(_(l)).append("</option>\n"); } + if (showRemove) + buf.append("<option value=\"remove\">").append(_("Remove")).append("</option>"); + buf.append("</select>\n"); + return buf.toString(); + } + + /** + * All the classes the log manager knows about, except ones that + * already have overrides + * @since 0.8.1 + */ + public String getNewClassBox() { + List<Log> logs = _context.logManager().getLogs(); + Set limits = _context.logManager().getLimits().keySet(); + TreeSet<String> sortedLogs = new TreeSet(); + + for (Log log : logs) { + String name = log.getName(); + if (!limits.contains(name)) + sortedLogs.add(name); + + // add higher classes of length 3 or more + int dots = 0; + int lastdot = -1; + int nextdot = 0; + while ((nextdot = name.indexOf('.', lastdot + 1)) > 0) { + if (++dots >= 3) { + String subst = name.substring(0, nextdot); + if (!limits.contains(subst)) + sortedLogs.add(subst); + } + lastdot = nextdot; + } + } + + StringBuilder buf = new StringBuilder(65536); + buf.append("<select name=\"newlogclass\">\n" + + "<option value=\"\" selected=\"true\">") + .append(_("Select a class to add")) + .append("</option>\n"); + + for (String l : sortedLogs) { + buf.append("<option value=\"").append(l).append("\">") + .append(l).append("</option>\n"); + } + buf.append("</select>\n"); + buf.append(getLogLevelBox("newloglevel", "WARN", false)); return buf.toString(); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java index d6eb2edd1c8745de06f11351ab949c5d93253663..f499b2ea43ac0b185ff949950143937922a201a3 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java @@ -44,13 +44,16 @@ public class LogsHelper extends HelperBase { } ******/ - private String formatMessages(List msgs) { + /** formats in reverse order */ + private String formatMessages(List<String> msgs) { + if (msgs.isEmpty()) + return "<p><i>" + _("No log messages") + "</i></p>"; boolean colorize = Boolean.valueOf(_context.getProperty("routerconsole.logs.color")).booleanValue(); StringBuilder buf = new StringBuilder(16*1024); buf.append("<ul>"); buf.append("<code>\n"); for (int i = msgs.size(); i > 0; i--) { - String msg = (String)msgs.get(i - 1); + String msg = msgs.get(i - 1); buf.append("<li>"); if (colorize) { String color; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index 244cc13a0f80e94009eb5a1de2a7c5b35914e4b8..a2c5632fa8e0e4d57e750d3f60447b3469f6d605 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -19,6 +19,8 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.TreeSet; @@ -34,6 +36,7 @@ import net.i2p.router.TunnelPoolSettings; import net.i2p.router.networkdb.kademlia.HashDistance; // debug import net.i2p.util.HexDump; // debug import net.i2p.util.ObjectCounter; +import net.i2p.util.OrderedProperties; import net.i2p.util.VersionComparator; public class NetDbRenderer { @@ -371,9 +374,11 @@ public class NetDbRenderer { int cost = addr.getCost(); if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10))) buf.append('[').append(_("cost")).append('=').append("" + cost).append("] "); - for (Iterator optIter = addr.getOptions().keySet().iterator(); optIter.hasNext(); ) { - String name = (String)optIter.next(); - String val = addr.getOptions().getProperty(name); + Properties p = new OrderedProperties(); + p.putAll(addr.getOptions()); + for (Map.Entry e : p.entrySet()) { + String name = (String) e.getKey(); + String val = (String) e.getValue(); buf.append('[').append(_(DataHelper.stripHTML(name))).append('=').append(DataHelper.stripHTML(val)).append("] "); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index b17dfbdade4264f1b98a579c86586e9404b44288..6958b350e9b2f2ccaf2e4126758ab75137f659af 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -563,7 +563,7 @@ public class PluginStarter implements Runnable { /** * http://jimlife.wordpress.com/2007/12/19/java-adding-new-classpath-at-runtime/ */ - public static void addPath(URL u) throws Exception { + private static void addPath(URL u) throws Exception { URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class urlClass = URLClassLoader.class; Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class}); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java index 26d026a1694eb8bb62ced52827a31fb4b82c989f..38e8f28b00ef014aa859c54b13d40e39b0be6e13 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java @@ -16,6 +16,7 @@ import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.OrderedProperties; +import net.i2p.util.SecureDirectory; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; import net.i2p.util.VersionComparator; @@ -150,7 +151,7 @@ public class PluginUpdateHandler extends UpdateHandler { public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) { updateStatus("<b>" + _("Plugin downloaded") + "</b>"); File f = new File(_updateFile); - File appDir = new File(_context.getConfigDir(), PLUGIN_DIR); + File appDir = new SecureDirectory(_context.getConfigDir(), PLUGIN_DIR); if ((!appDir.exists()) && (!appDir.mkdir())) { f.delete(); statusDone("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>"); @@ -273,7 +274,7 @@ public class PluginUpdateHandler extends UpdateHandler { return; } - File destDir = new File(appDir, appName); + File destDir = new SecureDirectory(appDir, appName); if (destDir.exists()) { if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) { to.delete(); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 05bedee374fed8b931d06f3811739f9445e03bfa..68533f7ef63c19cfce8b1b4a7c384b4b90f1b309 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -14,6 +14,7 @@ import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; +import net.i2p.util.SecureDirectory; import org.mortbay.http.DigestAuthenticator; import org.mortbay.http.HashUserRealm; @@ -62,7 +63,7 @@ public class RouterConsoleRunner { } public void startConsole() { - File workDir = new File(I2PAppContext.getGlobalContext().getTempDir(), "jetty-work"); + File workDir = new SecureDirectory(I2PAppContext.getGlobalContext().getTempDir(), "jetty-work"); boolean workDirRemoved = FileUtil.rmdir(workDir, false); if (!workDirRemoved) System.err.println("ERROR: Unable to remove Jetty temporary work directory"); @@ -115,7 +116,7 @@ public class RouterConsoleRunner { } _server.setRootWebApp(ROUTERCONSOLE); WebApplicationContext wac = _server.addWebApplication("/", _webAppsDir + ROUTERCONSOLE + ".war"); - File tmpdir = new File(workDir, ROUTERCONSOLE + "-" + _listenPort); + File tmpdir = new SecureDirectory(workDir, ROUTERCONSOLE + "-" + _listenPort); tmpdir.mkdir(); wac.setTempDirectory(tmpdir); baseHandler = new LocaleWebAppHandler(I2PAppContext.getGlobalContext()); @@ -130,7 +131,7 @@ public class RouterConsoleRunner { String enabled = props.getProperty(PREFIX + appName + ENABLED); if (! "false".equals(enabled)) { String path = new File(dir, fileNames[i]).getCanonicalPath(); - tmpdir = new File(workDir, appName + "-" + _listenPort); + tmpdir = new SecureDirectory(workDir, appName + "-" + _listenPort); WebAppStarter.addWebApp(I2PAppContext.getGlobalContext(), _server, appName, path, tmpdir); if (enabled == null) { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java index 0264d6307aa782147051ee26a3d7682e4850114d..1aac8ecc9fe7e3f70be3391c108a7c34666e7809 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java @@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.util.FileUtil; +import net.i2p.util.SecureDirectory; import org.mortbay.http.HttpContext; import org.mortbay.http.HttpListener; @@ -41,7 +42,7 @@ public class WebAppStarter { * @throws just about anything, caller would be wise to catch Throwable */ static void startWebApp(I2PAppContext ctx, Server server, String appName, String warPath) throws Exception { - File tmpdir = new File(ctx.getTempDir(), "jetty-work-" + appName + ctx.random().nextInt()); + File tmpdir = new SecureDirectory(ctx.getTempDir(), "jetty-work-" + appName + ctx.random().nextInt()); WebApplicationContext wac = addWebApp(ctx, server, appName, warPath, tmpdir); wac.start(); } @@ -73,7 +74,7 @@ public class WebAppStarter { warModTimes.put(warPath, new Long(newmod)); } else if (oldmod.longValue() < newmod) { // copy war to temporary directory - File warTmpDir = new File(ctx.getTempDir(), "war-copy-" + appName + ctx.random().nextInt()); + File warTmpDir = new SecureDirectory(ctx.getTempDir(), "war-copy-" + appName + ctx.random().nextInt()); warTmpDir.mkdir(); String tmpPath = (new File(warTmpDir, appName + ".war")).getAbsolutePath(); if (!FileUtil.copy(warPath, tmpPath, true)) diff --git a/apps/routerconsole/jsp/configlogging.jsp b/apps/routerconsole/jsp/configlogging.jsp index 3de8d95beea1ea5dae27fb6f555f55ca511bfc2d..ca30423dc211da59d53d88a34025bfe87b946ba6 100644 --- a/apps/routerconsole/jsp/configlogging.jsp +++ b/apps/routerconsole/jsp/configlogging.jsp @@ -46,6 +46,8 @@ </i></td> </tr><tr><td class="mediumtags" align="right"><b><%=intl._("Log level overrides")%>:</b></td> <td><jsp:getProperty name="logginghelper" property="logLevelTable" /></td> + </tr><tr><td class="mediumtags" align="right"><b><%=intl._("New override")%>:</b></td> + <td><jsp:getProperty name="logginghelper" property="newClassBox" /></td> </tr><tr><td colspan="2"><hr></td> </tr><tr class="tablefooter"><td colspan="2"> <div class="formaction"> <input type="reset" value="<%=intl._("Cancel")%>" > diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java index bc8c923699cf61f225625981264630d36dd046e8..e662ecc60a95174749196a406bf4636b2cfe53aa 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java @@ -148,16 +148,21 @@ public class Connection { } /** + * This doesn't "send a choke". Rather, it blocks if the outbound window is full, + * thus choking the sender that calls this. + * * Block until there is an open outbound packet slot or the write timeout * expires. + * PacketLocal is the only caller, generally with -1. * - * @param timeoutMs PacketLocal is the only caller, often with -1?????? - * @return true if the packet should be sent + * @param timeoutMs 0 or negative means wait forever, 5 minutes max + * @return true if the packet should be sent, false for a fatal error + * will return false after 5 minutes even if timeoutMs is <= 0. */ boolean packetSendChoke(long timeoutMs) { // if (false) return true; // <--- what the fuck?? long start = _context.clock().now(); - long writeExpire = start + timeoutMs; + long writeExpire = start + timeoutMs; // only used if timeoutMs > 0 boolean started = false; while (true) { long timeLeft = writeExpire - _context.clock().now(); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java index 627113abecbe3d78383bb673009e5fbb61404016..c6596b7c93b738167447ec5c3c7a0957d22274e9 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java @@ -646,6 +646,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl { return Boolean.valueOf(val).booleanValue(); } +/**** public static void main(String args[]) { Properties p = new Properties(); @@ -656,4 +657,5 @@ public class ConnectionOptions extends I2PSocketOptionsImpl { c = new ConnectionOptions(new I2PSocketOptionsImpl(p)); System.out.println("opts: " + c); } +****/ } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageInputStream.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageInputStream.java index 14b304b2688dfcb0e00b7d75f703476294c199d0..5a87d6be1490f9df0923925150d4fc589860cbdb 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/MessageInputStream.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageInputStream.java @@ -11,7 +11,7 @@ import java.util.Map; import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; -import net.i2p.util.ByteCache; +//import net.i2p.util.ByteCache; import net.i2p.util.Log; /** @@ -20,8 +20,8 @@ import net.i2p.util.Log; * */ public class MessageInputStream extends InputStream { - private I2PAppContext _context; - private Log _log; + private final I2PAppContext _context; + private final Log _log; /** * List of ByteArray objects of data ready to be read, * with the first ByteArray at index 0, and the next @@ -29,7 +29,7 @@ public class MessageInputStream extends InputStream { * that array. * */ - private List _readyDataBlocks; + private final List<ByteArray> _readyDataBlocks; private int _readyDataBlockIndex; /** highest message ID used in the readyDataBlocks */ private volatile long _highestReadyBlockId; @@ -40,7 +40,7 @@ public class MessageInputStream extends InputStream { * out of order when there are lower IDs not yet * received */ - private Map _notYetReadyBlocks; + private final Map<Long, ByteArray> _notYetReadyBlocks; /** * if we have received a flag saying there won't be later messages, EOF * after we have cleared what we have received. @@ -51,9 +51,9 @@ public class MessageInputStream extends InputStream { private int _readTimeout; private IOException _streamError; private long _readTotal; - private ByteCache _cache; + //private ByteCache _cache; - private byte[] _oneByte = new byte[1]; + private final byte[] _oneByte = new byte[1]; private final Object _dataLock; @@ -61,16 +61,12 @@ public class MessageInputStream extends InputStream { _context = ctx; _log = ctx.logManager().getLog(MessageInputStream.class); _readyDataBlocks = new ArrayList(4); - _readyDataBlockIndex = 0; _highestReadyBlockId = -1; _highestBlockId = -1; _readTimeout = -1; - _readTotal = 0; _notYetReadyBlocks = new HashMap(4); _dataLock = new Object(); - _closeReceived = false; - _locallyClosed = false; - _cache = ByteCache.getInstance(128, Packet.MAX_PAYLOAD_SIZE); + //_cache = ByteCache.getInstance(128, Packet.MAX_PAYLOAD_SIZE); } /** What is the highest block ID we've completely received through? @@ -140,10 +136,8 @@ public class MessageInputStream extends InputStream { if (num <= 0) return null; blocks = new long[num]; int i = 0; - for (Iterator iter = _notYetReadyBlocks.keySet().iterator(); iter.hasNext(); ) { - Long id = (Long)iter.next(); - blocks[i] = id.longValue(); - i++; + for (Long id : _notYetReadyBlocks.keySet()) { + blocks[i++] = id.longValue(); } } Arrays.sort(blocks); @@ -178,16 +172,15 @@ public class MessageInputStream extends InputStream { buf.append("Close received, ready bytes: "); long available = 0; for (int i = 0; i < _readyDataBlocks.size(); i++) - available += ((ByteArray)_readyDataBlocks.get(i)).getValid(); + available += _readyDataBlocks.get(i).getValid(); available -= _readyDataBlockIndex; buf.append(available); buf.append(" blocks: ").append(_readyDataBlocks.size()); buf.append(" not ready blocks: "); long notAvailable = 0; - for (Iterator iter = _notYetReadyBlocks.keySet().iterator(); iter.hasNext(); ) { - Long id = (Long)iter.next(); - ByteArray ba = (ByteArray)_notYetReadyBlocks.get(id); + for (Long id : _notYetReadyBlocks.keySet()) { + ByteArray ba = _notYetReadyBlocks.get(id); buf.append(id).append(" "); if (ba != null) @@ -237,7 +230,7 @@ public class MessageInputStream extends InputStream { long cur = _highestReadyBlockId + 1; // now pull in any previously pending blocks while (_notYetReadyBlocks.containsKey(new Long(cur))) { - ByteArray ba = (ByteArray)_notYetReadyBlocks.remove(new Long(cur)); + ByteArray ba = _notYetReadyBlocks.remove(new Long(cur)); if ( (ba != null) && (ba.getData() != null) && (ba.getValid() > 0) ) { _readyDataBlocks.add(ba); } @@ -341,7 +334,7 @@ public class MessageInputStream extends InputStream { return i; } else { // either was already ready, or we wait()ed and it arrived - ByteArray cur = (ByteArray)_readyDataBlocks.get(0); + ByteArray cur = _readyDataBlocks.get(0); byte rv = cur.getData()[cur.getOffset()+_readyDataBlockIndex]; _readyDataBlockIndex++; boolean removed = false; @@ -378,7 +371,7 @@ public class MessageInputStream extends InputStream { int numBytes = 0; synchronized (_dataLock) { for (int i = 0; i < _readyDataBlocks.size(); i++) { - ByteArray cur = (ByteArray)_readyDataBlocks.get(i); + ByteArray cur = _readyDataBlocks.get(i); if (i == 0) numBytes += cur.getValid() - _readyDataBlockIndex; else @@ -402,14 +395,13 @@ public class MessageInputStream extends InputStream { if (_locallyClosed) return 0; int numBytes = 0; for (int i = 0; i < _readyDataBlocks.size(); i++) { - ByteArray cur = (ByteArray)_readyDataBlocks.get(i); + ByteArray cur = _readyDataBlocks.get(i); if (i == 0) numBytes += cur.getValid() - _readyDataBlockIndex; else numBytes += cur.getValid(); } - for (Iterator iter = _notYetReadyBlocks.values().iterator(); iter.hasNext(); ) { - ByteArray cur = (ByteArray)iter.next(); + for (ByteArray cur : _notYetReadyBlocks.values()) { numBytes += cur.getValid(); } return numBytes; @@ -421,7 +413,7 @@ public class MessageInputStream extends InputStream { if (_locallyClosed) return 0; int numBytes = 0; for (int i = 0; i < _readyDataBlocks.size(); i++) { - ByteArray cur = (ByteArray)_readyDataBlocks.get(i); + ByteArray cur = _readyDataBlocks.get(i); if (i == 0) numBytes += cur.getValid() - _readyDataBlockIndex; else @@ -440,8 +432,7 @@ public class MessageInputStream extends InputStream { // we don't need the data, but we do need to keep track of the messageIds // received, so we can ACK accordingly - for (Iterator iter = _notYetReadyBlocks.values().iterator(); iter.hasNext(); ) { - ByteArray ba = (ByteArray)iter.next(); + for (ByteArray ba : _notYetReadyBlocks.values()) { ba.setData(null); //_cache.release(ba); } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java index 1a7f0afbe82e313c6793168c702a087039d3d6d5..fd10d679cec33b72c459ecfeb416747f59f8fefb 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java @@ -43,6 +43,10 @@ public class MessageOutputStream extends OutputStream { private long _sendPeriodBytes; private int _sendBps; + /** + * Since this is less than i2ptunnel's i2p.streaming.connectDelay default of 1000, + * we only wait 250 at the start. Guess that's ok, 1000 is too long anyway. + */ private static final int DEFAULT_PASSIVE_FLUSH_DELAY = 250; public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver) { @@ -273,8 +277,18 @@ public class MessageOutputStream extends OutputStream { } /** - * Flush the data already queued up, blocking until it has been - * delivered. + * Flush the data already queued up, blocking only if the outbound + * window is full. + * + * Prior to 0.8.1, this blocked until "delivered". + * "Delivered" meant "received an ACK from the far end", + * which is not the commom implementation of flush(), and really hurt the + * performance of i2psnark, which flush()ed frequently. + * Calling flush() would cause a complete window stall. + * + * As of 0.8.1, only wait for accept into the streaming output queue. + * This will speed up snark significantly, and allow us to flush() + * the initial data in I2PTunnelRunner, saving 250 ms. * * @throws IOException if the write fails */ @@ -283,6 +297,14 @@ public class MessageOutputStream extends OutputStream { /* @throws InterruptedIOException if the write times out * Documented here, but doesn't belong in the javadoc. */ + flush(true); + } + + /** + * @param wait_for_accept_only see discussion in close() code + * @@since 0.8.1 + */ + private void flush(boolean wait_for_accept_only) throws IOException { long begin = _context.clock().now(); WriteStatus ws = null; if (_log.shouldLog(Log.INFO) && _valid > 0) @@ -297,14 +319,28 @@ public class MessageOutputStream extends OutputStream { throwAnyError(); return; } - ws = _dataReceiver.writeData(_buf, 0, _valid); - _written += _valid; - _valid = 0; - locked_updateBufferSize(); - _lastFlushed = _context.clock().now(); - _dataLock.notifyAll(); + // if valid == 0 return ??? - no, this could flush a CLOSE packet too. + + // Yes, flush here, inside the data lock, and do all the waitForCompletion() stuff below + // (disabled) + if (!wait_for_accept_only) { + ws = _dataReceiver.writeData(_buf, 0, _valid); + _written += _valid; + _valid = 0; + locked_updateBufferSize(); + _lastFlushed = _context.clock().now(); + _dataLock.notifyAll(); + } } + // Skip all the waitForCompletion() stuff below, which is insanity, as of 0.8.1 + // must do this outside the data lock + if (wait_for_accept_only) { + flushAvailable(_dataReceiver, true); + return; + } + + // Wait a loooooong time, until we have the ACK if (_log.shouldLog(Log.DEBUG)) _log.debug("before waiting " + _writeTimeout + "ms for completion of " + ws); if (_closed && @@ -328,14 +364,28 @@ public class MessageOutputStream extends OutputStream { throwAnyError(); } + /** + * This does a flush, and BLOCKS until + * the CLOSE packet is acked. + */ @Override public void close() throws IOException { if (_closed) { synchronized (_dataLock) { _dataLock.notifyAll(); } return; } + // setting _closed before flush() will force flush() to send a CLOSE packet _closed = true; - flush(); + + // In 0.8.1 we rewrote flush() to only wait for accept into the window, + // not "completion" (i.e. ack from the far end). + // Unfortunately, that broke close(), at least in i2ptunnel HTTPClient. + // Symptom was premature close, i.e. incomplete pages and images. + // Possible cause - I2PTunnelRunner code? or the code here that follows flush()? + // It seems like we shouldn't have to wait for the far-end ACK for a close packet, + // should we? To be researched further. + // false -> wait for completion, not just accept. + flush(false); _log.debug("Output stream closed after writing " + _written); ByteArray ba = null; synchronized (_dataLock) { @@ -351,7 +401,11 @@ public class MessageOutputStream extends OutputStream { _dataCache.release(ba); } } - /** nonblocking close */ + + /** + * nonblocking close - + * Use outside of this package is deprecated, should be made package local + */ public void closeInternal() { _closed = true; if (_streamError == null) @@ -412,6 +466,8 @@ public class MessageOutputStream extends OutputStream { if (_log.shouldLog(Log.INFO) && _valid > 0) _log.info("flushAvailable() valid = " + _valid); synchronized (_dataLock) { + // if valid == 0 return ??? - no, this could flush a CLOSE packet too. + // _buf may be null, but the data receiver can handle that just fine, // deciding whether or not to send a packet ws = target.writeData(_buf, 0, _valid); @@ -457,14 +513,21 @@ public class MessageOutputStream extends OutputStream { /** Define a way to detect the status of a write */ public interface WriteStatus { - /** wait until the data written either fails or succeeds */ + /** + * Wait until the data written either fails or succeeds. + * Success means an ACK FROM THE FAR END. + * @param maxWaitMs -1 = forever + */ public void waitForCompletion(int maxWaitMs); + /** - * wait until the data written is accepted into the outbound pool, + * Wait until the data written is accepted into the outbound pool, + * (i.e. the outbound window is not full) * which we throttle rather than accept arbitrary data and queue - * @param maxWaitMs -1 = forever ? + * @param maxWaitMs -1 = forever */ public void waitForAccept(int maxWaitMs); + /** the write was accepted. aka did the socket not close? */ public boolean writeAccepted(); /** did the write fail? */ diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java index 674ff6179c531fcede87686b12d46b18a5f90823..cbe913e0505c028ac271af8399cb8b79e73c7bb5 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java @@ -194,7 +194,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat } /** - * @param maxWaitMs MessageOutputStream is the only caller, often with -1 ?????? + * Blocks until outbound window is not full. See Connection.packetSendChoke(). + * @param maxWaitMs MessageOutputStream is the only caller, generally with -1 */ public void waitForAccept(int maxWaitMs) { if (_connection == null) @@ -220,6 +221,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat + toString()); } + /** block until the packet is acked from the far end */ public void waitForCompletion(int maxWaitMs) { long expiration = _context.clock().now()+maxWaitMs; while (true) { diff --git a/apps/susidns/src/build.xml b/apps/susidns/src/build.xml index 55b662a9d380184965e0db1f8d3e9bc23e36351f..8244d37cfb7dc2d1ebad4a1e3390ec64cb7e6bf1 100644 --- a/apps/susidns/src/build.xml +++ b/apps/susidns/src/build.xml @@ -72,7 +72,6 @@ <include name="images/*.png"/> <include name="css.css"/> <include name="index.html"/> - <include name="WEB-INF/web-out.xml"/> <include name="WEB-INF/classes/${project}.properties"/> </fileset> </war> diff --git a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java index 63c4db85acb1f61c285ff7b7a2231ee927d552be..b6af8a12dada88fa0d30c0f359f158379d1fc681 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java @@ -38,6 +38,7 @@ import java.util.Properties; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.util.SecureFileOutputStream; public class AddressbookBean { @@ -330,7 +331,7 @@ public class AddressbookBean { String filename = properties.getProperty( getBook() + "_addressbook" ); - FileOutputStream fos = new FileOutputStream( ConfigBean.addressbookPrefix + filename ); + FileOutputStream fos = new SecureFileOutputStream( ConfigBean.addressbookPrefix + filename ); addressbook.store( fos, null ); try { fos.close(); diff --git a/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java b/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java index f6655757d8318977ee667baf4c8cbf013948fb05..ee65753564a0ff961f9432664fdfd5c8444b57bd 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java @@ -27,13 +27,13 @@ package i2p.susi.dns; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import net.i2p.I2PAppContext; +import net.i2p.util.SecureFileOutputStream; public class ConfigBean implements Serializable { @@ -111,7 +111,7 @@ public class ConfigBean implements Serializable { { File file = new File( configFileName ); try { - PrintWriter out = new PrintWriter( new FileOutputStream( file ) ); + PrintWriter out = new PrintWriter( new SecureFileOutputStream( file ) ); out.print( config ); out.flush(); out.close(); diff --git a/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java b/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java index 6b1a80576999b83406f5de862d881755b6d3f92a..176561acaf16c6934ae7c3a879b46b3f91d2cc63 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java @@ -28,12 +28,13 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Properties; +import net.i2p.util.SecureFileOutputStream; + public class SubscriptionsBean { private String action, fileName, content, serial, lastSerial; @@ -113,7 +114,7 @@ public class SubscriptionsBean { File file = new File( getFileName() ); try { - PrintWriter out = new PrintWriter( new FileOutputStream( file ) ); + PrintWriter out = new PrintWriter( new SecureFileOutputStream( file ) ); out.print( content ); out.flush(); out.close(); diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 637596c4f3858c7aaee8bd21b9206b35cc68c76b..98e7d6af9aeb81b2d9435ce6151c80ab8e936150 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -31,6 +31,7 @@ import net.i2p.util.KeyRing; import net.i2p.util.LogManager; //import net.i2p.util.PooledRandomSource; import net.i2p.util.RandomSource; +import net.i2p.util.SecureDirectory; /** * <p>Provide a base scope for accessing singletons that I2P exposes. Rather than @@ -218,7 +219,7 @@ public class I2PAppContext { // config defaults to base s = getProperty("i2p.dir.config"); if (s != null) { - _configDir = new File(s); + _configDir = new SecureDirectory(s); if (!_configDir.exists()) _configDir.mkdir(); } else { @@ -227,7 +228,7 @@ public class I2PAppContext { // router defaults to config s = getProperty("i2p.dir.router"); if (s != null) { - _routerDir = new File(s); + _routerDir = new SecureDirectory(s); if (!_routerDir.exists()) _routerDir.mkdir(); } else { @@ -241,7 +242,7 @@ public class I2PAppContext { // these all default to router s = getProperty("i2p.dir.log"); if (s != null) { - _logDir = new File(s); + _logDir = new SecureDirectory(s); if (!_logDir.exists()) _logDir.mkdir(); } else { @@ -249,7 +250,7 @@ public class I2PAppContext { } s = getProperty("i2p.dir.app"); if (s != null) { - _appDir = new File(s); + _appDir = new SecureDirectory(s); if (!_appDir.exists()) _appDir.mkdir(); } else { @@ -345,14 +346,14 @@ public class I2PAppContext { String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir")); // our random() probably isn't warmed up yet String f = "i2p-" + Math.abs((new java.util.Random()).nextInt()) + ".tmp"; - _tmpDir = new File(d, f); + _tmpDir = new SecureDirectory(d, f); if (_tmpDir.exists()) { // good or bad ? } else if (_tmpDir.mkdir()) { _tmpDir.deleteOnExit(); } else { System.err.println("Could not create temp dir " + _tmpDir.getAbsolutePath()); - _tmpDir = new File(_routerDir, "tmp"); + _tmpDir = new SecureDirectory(_routerDir, "tmp"); _tmpDir.mkdir(); } } diff --git a/core/java/src/net/i2p/client/HandlerImpl.java b/core/java/src/net/i2p/client/HandlerImpl.java index 98d1e76ff25bb5502a14369d4d595fa2a1ac5929..d1b5100e2754af29f51fc6656068d19227e98cd0 100644 --- a/core/java/src/net/i2p/client/HandlerImpl.java +++ b/core/java/src/net/i2p/client/HandlerImpl.java @@ -18,9 +18,9 @@ import net.i2p.util.Log; * @author jrandom */ abstract class HandlerImpl implements I2CPMessageHandler { - protected Log _log; - private int _type; - protected I2PAppContext _context; + protected final Log _log; + private final int _type; + protected final I2PAppContext _context; public HandlerImpl(I2PAppContext context, int type) { _context = context; diff --git a/core/java/src/net/i2p/client/I2PClient.java b/core/java/src/net/i2p/client/I2PClient.java index 9a732c3dba0a20fdf9d36560937c8886c254de1f..941549d5372bf453f949fd7af1c62b9a22079f3f 100644 --- a/core/java/src/net/i2p/client/I2PClient.java +++ b/core/java/src/net/i2p/client/I2PClient.java @@ -34,6 +34,8 @@ public interface I2PClient { public final static String PROP_RELIABILITY_BEST_EFFORT = "BestEffort"; /** Reliability value: guaranteed */ public final static String PROP_RELIABILITY_GUARANTEED = "Guaranteed"; + /** @since 0.8.1 */ + public final static String PROP_RELIABILITY_NONE = "none"; /** protocol flag that must be sent when opening the i2cp connection to the router */ public final static int PROTOCOL_BYTE = 0x2A; @@ -64,4 +66,4 @@ public interface I2PClient { * @return newly created destination */ public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException; -} \ No newline at end of file +} diff --git a/core/java/src/net/i2p/client/I2PSessionImpl2.java b/core/java/src/net/i2p/client/I2PSessionImpl2.java index d8d8c8dfce202f8dd09c4dc37b1b4a6f42f0521d..62b0d3d08b9fcd1d398ad5c9fe2628881b7b7513 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl2.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl2.java @@ -33,12 +33,14 @@ import net.i2p.util.Log; class I2PSessionImpl2 extends I2PSessionImpl { /** set of MessageState objects, representing all of the messages in the process of being sent */ - private /* FIXME final FIXME */ Set _sendingStates; + private /* FIXME final FIXME */ Set<MessageState> _sendingStates; /** max # seconds to wait for confirmation of the message send */ private final static long SEND_TIMEOUT = 60 * 1000; // 60 seconds to send /** should we gzip each payload prior to sending it? */ private final static boolean SHOULD_COMPRESS = true; private final static boolean SHOULD_DECOMPRESS = true; + /** Don't expect any MSMs from the router for outbound traffic @since 0.8.1 */ + private boolean _noEffort; /** for extension */ public I2PSessionImpl2() {} @@ -53,6 +55,8 @@ class I2PSessionImpl2 extends I2PSessionImpl { super(ctx, destKeyStream, options); _log = ctx.logManager().getLog(I2PSessionImpl2.class); _sendingStates = new HashSet(32); + // default is BestEffort + _noEffort = "none".equalsIgnoreCase(options.getProperty(I2PClient.PROP_RELIABILITY)); ctx.statManager().createRateStat("i2cp.sendBestEffortTotalTime", "how long to do the full sendBestEffort call?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortStage0", "first part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); @@ -60,15 +64,16 @@ class I2PSessionImpl2 extends I2PSessionImpl { //ctx.statManager().createRateStat("i2cp.sendBestEffortStage2", "third part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortStage3", "fourth part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortStage4", "fifth part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); - _context.statManager().createRateStat("i2cp.receiveStatusTime.0", "How long it took to get status=0 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.receiveStatusTime.1", "How long it took to get status=1 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.receiveStatusTime.2", "How long it took to get status=2 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.receiveStatusTime.3", "How long it took to get status=3 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 60*1000, 30*60*1000 }); - _context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 60*1000, 30*60*1000 }); + //_context.statManager().createRateStat("i2cp.receiveStatusTime.0", "How long it took to get status=0 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); + _context.statManager().createRateStat("i2cp.receiveStatusTime.1", "How long it took to get status=1 back", "i2cp", new long[] { 10*60*1000 }); + // best effort codes unused + //_context.statManager().createRateStat("i2cp.receiveStatusTime.2", "How long it took to get status=2 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); + //_context.statManager().createRateStat("i2cp.receiveStatusTime.3", "How long it took to get status=3 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); + _context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 10*60*1000 }); + _context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 10*60*1000 }); + _context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 10*60*1000 }); + _context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 30*60*1000 }); + _context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 }); } protected long getTimeout() { @@ -186,7 +191,10 @@ class I2PSessionImpl2 extends I2PSessionImpl { } _context.statManager().addRateData("i2cp.tx.msgCompressed", compressed, 0); _context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0); - return sendBestEffort(dest, payload, keyUsed, tagsSent, expires); + if (_noEffort) + return sendNoEffort(dest, payload, expires); + else + return sendBestEffort(dest, payload, keyUsed, tagsSent, expires); } /** @@ -213,6 +221,9 @@ class I2PSessionImpl2 extends I2PSessionImpl { private static final int NUM_TAGS = 50; /** + * TODO - Don't need to save MessageState since actuallyWait is false... + * But for now just use sendNoEffort() instead. + * * @param keyUsed unused - no end-to-end crypto * @param tagsSent unused - no end-to-end crypto */ @@ -257,7 +268,7 @@ class I2PSessionImpl2 extends I2PSessionImpl { + "ms left, " + oldTags + " tags known and " + (tag == null ? "no tag" : " a valid tag")); } - + if (false) // rekey newKey = _context.keyGenerator().generateSessionKey(); @@ -371,6 +382,37 @@ class I2PSessionImpl2 extends I2PSessionImpl { return found; } + /** + * Same as sendBestEffort(), except we do not expect any MessageStatusMessage responses - + * not for accepted, or success, or failure. + * So we don't create a MessageState and save it on the _sendingStates HashSet + * + * @return true always + * @since 0.8.1 + */ + protected boolean sendNoEffort(Destination dest, byte payload[], long expires) + throws I2PSessionException { + // nonce always 0 + _producer.sendMessage(this, dest, 0, payload, null, null, null, null, expires); + return true; + } + + /** + * Only call this with nonzero status, i.e. for outbound messages + * whose MessageState may be queued on _sendingStates. + * + * Even when using sendBestEffort(), this is a waste, because the + * MessageState is removed from _sendingStates immediately and + * so the lookup here fails. + * And iterating through the HashSet instead of having a map + * is bad too. + * + * This is now pretty much avoided since streaming now sets + * i2cp.messageReliability = none, which forces sendNoEffort() instead of sendBestEffort(), + * so the router won't send us any MSM's for outbound traffic. + * + * @param status != 0 + */ @Override public void receiveStatus(int msgId, long nonce, int status) { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Received status " + status + " for msgId " + msgId + " / " + nonce); @@ -413,12 +455,13 @@ class I2PSessionImpl2 extends I2PSessionImpl { case 1: _context.statManager().addRateData("i2cp.receiveStatusTime.1", lifetime, 0); break; - case 2: - _context.statManager().addRateData("i2cp.receiveStatusTime.2", lifetime, 0); - break; - case 3: - _context.statManager().addRateData("i2cp.receiveStatusTime.3", lifetime, 0); - break; + // best effort codes unused + //case 2: + // _context.statManager().addRateData("i2cp.receiveStatusTime.2", lifetime, 0); + // break; + //case 3: + // _context.statManager().addRateData("i2cp.receiveStatusTime.3", lifetime, 0); + // break; case 4: _context.statManager().addRateData("i2cp.receiveStatusTime.4", lifetime, 0); break; diff --git a/core/java/src/net/i2p/client/MessageState.java b/core/java/src/net/i2p/client/MessageState.java index 84ec9ad6aa22b205bf41f4c33f3fee5b48b5113e..a67b5277044106eb323ef0c250314490acb99a18 100644 --- a/core/java/src/net/i2p/client/MessageState.java +++ b/core/java/src/net/i2p/client/MessageState.java @@ -37,13 +37,7 @@ class MessageState { _context = ctx; _nonce = nonce; _prefix = prefix + "[" + _stateId + "]: "; - _id = null; _receivedStatus = new HashSet(); - _cancelled = false; - _key = null; - _newKey = null; - _tags = null; - _to = null; _created = ctx.clock().now(); //ctx.statManager().createRateStat("i2cp.checkStatusTime", "how long it takes to go through the states", "i2cp", new long[] { 60*1000 }); } diff --git a/core/java/src/net/i2p/client/SessionIdleTimer.java b/core/java/src/net/i2p/client/SessionIdleTimer.java index dad736d97f87440613132348c47e6edaf747da71..91c1c4b0fbc263c4f6f757cd63113cfbc9f008f0 100644 --- a/core/java/src/net/i2p/client/SessionIdleTimer.java +++ b/core/java/src/net/i2p/client/SessionIdleTimer.java @@ -18,13 +18,13 @@ import net.i2p.util.SimpleTimer; * * @author zzz */ -public class SessionIdleTimer implements SimpleTimer.TimedEvent { +class SessionIdleTimer implements SimpleTimer.TimedEvent { public static final long MINIMUM_TIME = 5*60*1000; private static final long DEFAULT_REDUCE_TIME = 20*60*1000; private static final long DEFAULT_CLOSE_TIME = 30*60*1000; private final static Log _log = new Log(SessionIdleTimer.class); - private I2PAppContext _context; - private I2PSessionImpl _session; + private final I2PAppContext _context; + private final I2PSessionImpl _session; private boolean _reduceEnabled; private int _reduceQuantity; private long _reduceTime; @@ -36,7 +36,6 @@ public class SessionIdleTimer implements SimpleTimer.TimedEvent { /** * reduce, shutdown, or both must be true */ - /* FIXME Exporting non-public type through public API FIXME */ public SessionIdleTimer(I2PAppContext context, I2PSessionImpl session, boolean reduce, boolean shutdown) { _context = context; _session = session; diff --git a/core/java/src/net/i2p/client/naming/EepGetNamingService.java b/core/java/src/net/i2p/client/naming/EepGetNamingService.java index d8331796712770adeacfe73592675b7f85277f83..737e33423869ae32bf09cbd623716dcf18da8085 100644 --- a/core/java/src/net/i2p/client/naming/EepGetNamingService.java +++ b/core/java/src/net/i2p/client/naming/EepGetNamingService.java @@ -66,6 +66,10 @@ public class EepGetNamingService extends NamingService { hostname = hostname.toLowerCase(); + // If you want b32, chain with HostsTxtNamingService + if (hostname.length() == 60 && hostname.endsWith(".b32.i2p")) + return null; + // check the cache Destination d = getCache(hostname); if (d != null) diff --git a/core/java/src/net/i2p/client/naming/ExecNamingService.java b/core/java/src/net/i2p/client/naming/ExecNamingService.java index 446e907c477af9f8fa6c64c8a6b4c5d9dad1061c..118f06eac55c3c8e35825ab48f1ef71794262164 100644 --- a/core/java/src/net/i2p/client/naming/ExecNamingService.java +++ b/core/java/src/net/i2p/client/naming/ExecNamingService.java @@ -66,6 +66,10 @@ public class ExecNamingService extends NamingService { hostname = hostname.toLowerCase(); + // If you want b32, chain with HostsTxtNamingService + if (hostname.length() == 60 && hostname.endsWith(".b32.i2p")) + return null; + // check the cache Destination d = getCache(hostname); if (d != null) diff --git a/core/java/src/net/i2p/client/naming/LookupDest.java b/core/java/src/net/i2p/client/naming/LookupDest.java index 1a8a1632b887742f6a8d78967108eb89704fd4ba..2bbb2eeb6a2a5550fd5260a54366d2d879954e5b 100644 --- a/core/java/src/net/i2p/client/naming/LookupDest.java +++ b/core/java/src/net/i2p/client/naming/LookupDest.java @@ -27,6 +27,7 @@ class LookupDest { protected LookupDest(I2PAppContext context) {} + /** @param key 52 chars (do not include the .b32.i2p suffix) */ static Destination lookupBase32Hash(I2PAppContext ctx, String key) { byte[] h = Base32.decode(key); if (h == null) @@ -44,6 +45,7 @@ class LookupDest { } ****/ + /** @param h 32 byte hash */ static Destination lookupHash(I2PAppContext ctx, byte[] h) { Hash key = new Hash(h); Destination rv = null; diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java index a7098d799335348048aa0fc73953b1d5f318cb79..fc9a5341400a896af1335ab1472d2e9ec38e54b0 100644 --- a/core/java/src/net/i2p/client/naming/NamingService.java +++ b/core/java/src/net/i2p/client/naming/NamingService.java @@ -32,7 +32,7 @@ public abstract class NamingService { public static final String PROP_IMPL = "i2p.naming.impl"; private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService"; - protected static final int CACHE_MAX_SIZE = 8; + protected static final int CACHE_MAX_SIZE = 16; /** @@ -107,7 +107,7 @@ public abstract class NamingService { * The service may override the age and/or size limit */ /** Don't know why a dest would ever change but keep this short anyway */ - protected static final long CACHE_MAX_AGE = 60*1000; + protected static final long CACHE_MAX_AGE = 7*60*1000; private class CacheEntry { public Destination dest; @@ -174,4 +174,11 @@ public abstract class NamingService { return ce.dest; } } + + /** @since 0.8.1 */ + public void clearCache() { + synchronized (_cache) { + _cache.clear(); + } + } } diff --git a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java index 5084ae2b0a836d7d2c3e9416da754451e0fceacb..5d39c1578b755388f3a54089e09ff14ba6c5e48a 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java +++ b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java @@ -1,7 +1,6 @@ package net.i2p.crypto; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; /** * Cache the objects used in CryptixRijndael_Algorithm.makeKey to reduce @@ -11,7 +10,7 @@ import java.util.List; * */ public final class CryptixAESKeyCache { - private final List _availableKeys; + private final LinkedBlockingQueue<KeyCacheEntry> _availableKeys; private static final int KEYSIZE = 32; // 256bit AES private static final int BLOCKSIZE = 16; @@ -22,7 +21,7 @@ public final class CryptixAESKeyCache { private static final int MAX_KEYS = 64; public CryptixAESKeyCache() { - _availableKeys = new ArrayList(MAX_KEYS); + _availableKeys = new LinkedBlockingQueue(MAX_KEYS); } /** @@ -30,10 +29,9 @@ public final class CryptixAESKeyCache { * */ public final KeyCacheEntry acquireKey() { - synchronized (_availableKeys) { - if (!_availableKeys.isEmpty()) - return (KeyCacheEntry)_availableKeys.remove(0); - } + KeyCacheEntry rv = _availableKeys.poll(); + if (rv != null) + return rv; return createNew(); } @@ -42,10 +40,7 @@ public final class CryptixAESKeyCache { * */ public final void releaseKey(KeyCacheEntry key) { - synchronized (_availableKeys) { - if (_availableKeys.size() < MAX_KEYS) - _availableKeys.add(key); - } + _availableKeys.offer(key); } public static final KeyCacheEntry createNew() { diff --git a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java b/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java index 2c1e57d8e5795d52a3ddfe05de1085f89ff23b7a..b438f2b471df9025e9de49b2cbc229ba5d92ed12 100644 --- a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java +++ b/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java @@ -65,35 +65,17 @@ public class DHSessionKeyBuilder { public final static String PROP_DH_PRECALC_MIN = "crypto.dh.precalc.min"; public final static String PROP_DH_PRECALC_MAX = "crypto.dh.precalc.max"; public final static String PROP_DH_PRECALC_DELAY = "crypto.dh.precalc.delay"; - public final static String DEFAULT_DH_PRECALC_MIN = "5"; - public final static String DEFAULT_DH_PRECALC_MAX = "50"; - public final static String DEFAULT_DH_PRECALC_DELAY = "10000"; + public final static int DEFAULT_DH_PRECALC_MIN = 5; + public final static int DEFAULT_DH_PRECALC_MAX = 50; + public final static int DEFAULT_DH_PRECALC_DELAY = 10000; static { I2PAppContext ctx = _context; ctx.statManager().createRateStat("crypto.dhGeneratePublicTime", "How long it takes to create x and X", "Encryption", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); ctx.statManager().createRateStat("crypto.dhCalculateSessionTime", "How long it takes to create the session key", "Encryption", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); - try { - int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN)); - MIN_NUM_BUILDERS = val; - } catch (Throwable t) { - int val = Integer.parseInt(DEFAULT_DH_PRECALC_MIN); - MIN_NUM_BUILDERS = val; - } - try { - int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX)); - MAX_NUM_BUILDERS = val; - } catch (Throwable t) { - int val = Integer.parseInt(DEFAULT_DH_PRECALC_MAX); - MAX_NUM_BUILDERS = val; - } - try { - int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY)); - CALC_DELAY = val; - } catch (Throwable t) { - int val = Integer.parseInt(DEFAULT_DH_PRECALC_DELAY); - CALC_DELAY = val; - } + MIN_NUM_BUILDERS = ctx.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN); + MAX_NUM_BUILDERS = ctx.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX); + CALC_DELAY = ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY); if (_log.shouldLog(Log.DEBUG)) _log.debug("DH Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index b083cc24800395b4d22f301b33e50c9211f1e5cc..941774253ca91a774199e3eef09194f412362142 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -41,6 +41,10 @@ import net.i2p.data.SigningPublicKey; import net.i2p.util.Log; import net.i2p.util.NativeBigInteger; +/** + * Params and rv's changed from Hash to SHA1Hash for version 0.8.1 + * There shouldn't be any external users of those variants. + */ public class DSAEngine { private Log _log; private I2PAppContext _context; @@ -61,7 +65,9 @@ public class DSAEngine { public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) { return verifySignature(signature, calculateHash(in), verifyingKey); } - public boolean verifySignature(Signature signature, Hash hash, SigningPublicKey verifyingKey) { + + /** @param hash SHA-1 hash, NOT a SHA-256 hash */ + public boolean verifySignature(Signature signature, SHA1Hash hash, SigningPublicKey verifyingKey) { long start = _context.clock().now(); try { @@ -111,17 +117,18 @@ public class DSAEngine { } public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) { if ((signingKey == null) || (data == null) || (data.length <= 0)) return null; - Hash h = calculateHash(data, offset, length); + SHA1Hash h = calculateHash(data, offset, length); return sign(h, signingKey); } public Signature sign(InputStream in, SigningPrivateKey signingKey) { if ((signingKey == null) || (in == null) ) return null; - Hash h = calculateHash(in); + SHA1Hash h = calculateHash(in); return sign(h, signingKey); } - public Signature sign(Hash hash, SigningPrivateKey signingKey) { + /** @param hash SHA-1 hash, NOT a SHA-256 hash */ + public Signature sign(SHA1Hash hash, SigningPrivateKey signingKey) { if ((signingKey == null) || (hash == null)) return null; long start = _context.clock().now(); @@ -186,7 +193,8 @@ public class DSAEngine { return sig; } - public Hash calculateHash(InputStream in) { + /** @return hash SHA-1 hash, NOT a SHA-256 hash */ + public SHA1Hash calculateHash(InputStream in) { SHA1 digest = new SHA1(); byte buf[] = new byte[64]; int read = 0; @@ -199,14 +207,15 @@ public class DSAEngine { _log.warn("Unable to hash the stream", ioe); return null; } - return new Hash(digest.engineDigest()); + return new SHA1Hash(digest.engineDigest()); } - public static Hash calculateHash(byte[] source, int offset, int len) { + /** @return hash SHA-1 hash, NOT a SHA-256 hash */ + public static SHA1Hash calculateHash(byte[] source, int offset, int len) { SHA1 h = new SHA1(); h.engineUpdate(source, offset, len); byte digested[] = h.digest(); - return new Hash(digested); + return new SHA1Hash(digested); } public static void main(String args[]) { diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java index bf053210942f9e29025fd28889d13d01fcff16aa..3fc554639f5c6c6323cf6d303cd95fe3b4002e44 100644 --- a/core/java/src/net/i2p/crypto/HMAC256Generator.java +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -15,16 +15,16 @@ import org.bouncycastle.crypto.macs.I2PHMac; * in {@link org.bouncycastle.crypto.macs.I2PHMac} and * {@link org.bouncycastle.crypto.digests.MD5Digest}. * + * deprecated unused */ public class HMAC256Generator extends HMACGenerator { public HMAC256Generator(I2PAppContext context) { super(context); } @Override protected I2PHMac acquire() { - synchronized (_available) { - if (!_available.isEmpty()) - return (I2PHMac)_available.remove(0); - } + I2PHMac rv = _available.poll(); + if (rv != null) + return rv; // the HMAC is hardcoded to use SHA256 digest size // for backwards compatability. next time we have a backwards // incompatible change, we should update this by removing ", 32" @@ -43,6 +43,7 @@ public class HMAC256Generator extends HMACGenerator { } +/****** public static void main(String args[]) { I2PAppContext ctx = I2PAppContext.getGlobalContext(); byte data[] = new byte[64]; @@ -51,4 +52,5 @@ public class HMAC256Generator extends HMACGenerator { Hash mac = ctx.hmac256().calculate(key, data); System.out.println(Base64.encode(mac.getData())); } +******/ } diff --git a/core/java/src/net/i2p/crypto/HMACGenerator.java b/core/java/src/net/i2p/crypto/HMACGenerator.java index aed444ed06118259ac253745cdc4d715510e0ed5..237c650550306ef5be52b57c64746d592202d203 100644 --- a/core/java/src/net/i2p/crypto/HMACGenerator.java +++ b/core/java/src/net/i2p/crypto/HMACGenerator.java @@ -1,8 +1,7 @@ package net.i2p.crypto; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; @@ -22,14 +21,14 @@ import org.bouncycastle.crypto.macs.I2PHMac; public class HMACGenerator { private I2PAppContext _context; /** set of available HMAC instances for calculate */ - protected final List _available; + protected final LinkedBlockingQueue<I2PHMac> _available; /** set of available byte[] buffers for verify */ - private final List _availableTmp; + private final LinkedBlockingQueue<byte[]> _availableTmp; public HMACGenerator(I2PAppContext context) { _context = context; - _available = new ArrayList(32); - _availableTmp = new ArrayList(32); + _available = new LinkedBlockingQueue(32); + _availableTmp = new LinkedBlockingQueue(32); } /** @@ -88,39 +87,30 @@ public class HMACGenerator { } protected I2PHMac acquire() { - synchronized (_available) { - if (!_available.isEmpty()) - return (I2PHMac)_available.remove(0); - } + I2PHMac rv = _available.poll(); + if (rv != null) + return rv; // the HMAC is hardcoded to use SHA256 digest size // for backwards compatability. next time we have a backwards // incompatible change, we should update this by removing ", 32" return new I2PHMac(new MD5Digest(), 32); } - private void release(Mac mac) { - synchronized (_available) { - if (_available.size() < 64) - _available.add(mac); - } + + private void release(I2PHMac mac) { + _available.offer(mac); } // temp buffers for verify(..) private byte[] acquireTmp() { - byte rv[] = null; - synchronized (_availableTmp) { - if (!_availableTmp.isEmpty()) - rv = (byte[])_availableTmp.remove(0); - } + byte rv[] = _availableTmp.poll(); if (rv != null) Arrays.fill(rv, (byte)0x0); else rv = new byte[Hash.HASH_LENGTH]; return rv; } + private void releaseTmp(byte tmp[]) { - synchronized (_availableTmp) { - if (_availableTmp.size() < 64) - _availableTmp.add((Object)tmp); - } + _availableTmp.offer(tmp); } } diff --git a/core/java/src/net/i2p/crypto/SHA1.java b/core/java/src/net/i2p/crypto/SHA1.java index d8a0ac1c20c42667c41ae2ad45ab7c66dc78a8d4..6dbdee074bae5567607c70b86d315f7b28475ddc 100644 --- a/core/java/src/net/i2p/crypto/SHA1.java +++ b/core/java/src/net/i2p/crypto/SHA1.java @@ -63,7 +63,7 @@ public final class SHA1 extends MessageDigest implements Cloneable { /** * This implementation returns a fixed-size digest. */ - private static final int HASH_LENGTH = 20; // bytes == 160 bits + static final int HASH_LENGTH = 20; // bytes == 160 bits /** * Private context for incomplete blocks and padding bytes. diff --git a/core/java/src/net/i2p/crypto/SHA1Hash.java b/core/java/src/net/i2p/crypto/SHA1Hash.java new file mode 100644 index 0000000000000000000000000000000000000000..acbb68d4b8789455f587683f63e16d37c0f9826f --- /dev/null +++ b/core/java/src/net/i2p/crypto/SHA1Hash.java @@ -0,0 +1,81 @@ +package net.i2p.crypto; + +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.DataStructureImpl; + +/** + * Because DSAEngine was abusing Hash for 20-byte hashes + * + * @since 0.8.1 + * @author zzz + */ +public class SHA1Hash extends DataStructureImpl { + private byte[] _data; + private int _cachedHashCode; + + public final static int HASH_LENGTH = SHA1.HASH_LENGTH; + + /** @throws IllegalArgumentException if data is not 20 bytes (null is ok) */ + public SHA1Hash(byte data[]) { + setData(data); + } + + public byte[] getData() { + return _data; + } + + /** @throws IllegalArgumentException if data is not 20 bytes (null is ok) */ + public void setData(byte[] data) { + // FIXME DSAEngine uses a SHA-1 "Hash" as parameters and return values! + if (data != null && data.length != HASH_LENGTH) + throw new IllegalArgumentException("Hash must be 20 bytes"); + _data = data; + _cachedHashCode = calcHashCode(); + } + + /** @throws IOException always */ + public void readBytes(InputStream in) throws DataFormatException, IOException { + throw new IOException("unimplemented"); + } + + /** @throws IOException always */ + public void writeBytes(OutputStream out) throws DataFormatException, IOException { + throw new IOException("unimplemented"); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof SHA1Hash)) return false; + return DataHelper.eq(_data, ((SHA1Hash) obj)._data); + } + + /** a Hash is a hash, so just use the first 4 bytes for speed */ + @Override + public int hashCode() { + return _cachedHashCode; + } + + /** a Hash is a hash, so just use the first 4 bytes for speed */ + private int calcHashCode() { + int rv = 0; + if (_data != null) { + for (int i = 0; i < 4; i++) + rv ^= (_data[i] << (i*8)); + } + return rv; + } +} diff --git a/core/java/src/net/i2p/crypto/YKGenerator.java b/core/java/src/net/i2p/crypto/YKGenerator.java index ad68ebec1fd56a2c2e78c6c18b383f9deadb89c4..af83f2c9298234d9b43001ae3ca66f069d3339b0 100644 --- a/core/java/src/net/i2p/crypto/YKGenerator.java +++ b/core/java/src/net/i2p/crypto/YKGenerator.java @@ -10,24 +10,22 @@ package net.i2p.crypto; */ import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.util.Clock; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.NativeBigInteger; -import net.i2p.util.RandomSource; /** * Precalculate the Y and K for ElGamal encryption operations. * * This class precalcs a set of values on its own thread, using those transparently * when a new instance is created. By default, the minimum threshold for creating - * new values for the pool is 5, and the max pool size is 10. Whenever the pool has + * new values for the pool is 20, and the max pool size is 50. Whenever the pool has * less than the minimum, it fills it up again to the max. There is a delay after - * each precalculation so that the CPU isn't hosed during startup (defaulting to 10 seconds). + * each precalculation so that the CPU isn't hosed during startup. * These three parameters are controlled by java environmental variables and * can be adjusted via: * -Dcrypto.yk.precalc.min=40 -Dcrypto.yk.precalc.max=100 -Dcrypto.yk.precalc.delay=60000 @@ -39,51 +37,36 @@ import net.i2p.util.RandomSource; * @author jrandom */ class YKGenerator { - private final static Log _log = new Log(YKGenerator.class); - private static int MIN_NUM_BUILDERS = -1; - private static int MAX_NUM_BUILDERS = -1; - private static int CALC_DELAY = -1; - /* FIXME final type if you are to syncronize FIXME */ - private static volatile List _values = new ArrayList(50); // list of BigInteger[] values (y and k) - private static Thread _precalcThread = null; + //private final static Log _log = new Log(YKGenerator.class); + private static final int MIN_NUM_BUILDERS; + private static final int MAX_NUM_BUILDERS; + private static final int CALC_DELAY; + private static final LinkedBlockingQueue<BigInteger[]> _values = new LinkedBlockingQueue(50); // list of BigInteger[] values (y and k) + private static final Thread _precalcThread; + private static final I2PAppContext ctx; public final static String PROP_YK_PRECALC_MIN = "crypto.yk.precalc.min"; public final static String PROP_YK_PRECALC_MAX = "crypto.yk.precalc.max"; public final static String PROP_YK_PRECALC_DELAY = "crypto.yk.precalc.delay"; - public final static String DEFAULT_YK_PRECALC_MIN = "10"; - public final static String DEFAULT_YK_PRECALC_MAX = "30"; - public final static String DEFAULT_YK_PRECALC_DELAY = "10000"; + public final static int DEFAULT_YK_PRECALC_MIN = 20; + public final static int DEFAULT_YK_PRECALC_MAX = 50; + public final static int DEFAULT_YK_PRECALC_DELAY = 200; /** check every 30 seconds whether we have less than the minimum */ - private final static long CHECK_DELAY = 30 * 1000; + private static long CHECK_DELAY = 30 * 1000; static { - I2PAppContext ctx = I2PAppContext.getGlobalContext(); - try { - int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN)); - MIN_NUM_BUILDERS = val; - } catch (Throwable t) { - int val = Integer.parseInt(DEFAULT_YK_PRECALC_MIN); - MIN_NUM_BUILDERS = val; - } - try { - int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX)); - MAX_NUM_BUILDERS = val; - } catch (Throwable t) { - int val = Integer.parseInt(DEFAULT_YK_PRECALC_MAX); - MAX_NUM_BUILDERS = val; - } - try { - int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY)); - CALC_DELAY = val; - } catch (Throwable t) { - int val = Integer.parseInt(DEFAULT_YK_PRECALC_DELAY); - CALC_DELAY = val; - } + ctx = I2PAppContext.getGlobalContext(); + MIN_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN); + MAX_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX); + CALC_DELAY = ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY); + + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("ElGamal YK Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " + // + CALC_DELAY + ")"); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("ElGamal YK Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " - + CALC_DELAY + ")"); + ctx.statManager().createRateStat("crypto.YKUsed", "Need a YK from the queue", "Encryption", new long[] { 60*60*1000 }); + ctx.statManager().createRateStat("crypto.YKEmpty", "YK queue empty", "Encryption", new long[] { 60*60*1000 }); _precalcThread = new I2PThread(new YKPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS)); _precalcThread.setName("YK Precalc"); @@ -93,45 +76,36 @@ class YKGenerator { } private static final int getSize() { - synchronized (_values) { - return _values.size(); - } + return _values.size(); } - private static final int addValues(BigInteger yk[]) { - int sz = 0; - synchronized (_values) { - _values.add(yk); - sz = _values.size(); - } - return sz; + /** @return true if successful, false if full */ + private static final boolean addValues(BigInteger yk[]) { + return _values.offer(yk); } + /** @return rv[0] = Y; rv[1] = K */ public static BigInteger[] getNextYK() { - if (true) { - synchronized (_values) { - if (!_values.isEmpty()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sufficient precalculated YK values - fetch the existing"); - return (BigInteger[]) _values.remove(0); - } - } - } - if (_log.shouldLog(Log.INFO)) _log.info("Insufficient precalculated YK values - create a new one"); + ctx.statManager().addRateData("crypto.YKUsed", 1, 0); + BigInteger[] rv = _values.poll(); + if (rv != null) + return rv; + ctx.statManager().addRateData("crypto.YKEmpty", 1, 0); return generateYK(); } private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02}); + /** @return rv[0] = Y; rv[1] = K */ private static final BigInteger[] generateYK() { NativeBigInteger k = null; BigInteger y = null; - long t0 = 0; - long t1 = 0; + //long t0 = 0; + //long t1 = 0; while (k == null) { - t0 = Clock.getInstance().now(); - k = new NativeBigInteger(KeyGenerator.PUBKEY_EXPONENT_SIZE, RandomSource.getInstance()); - t1 = Clock.getInstance().now(); + //t0 = Clock.getInstance().now(); + k = new NativeBigInteger(KeyGenerator.PUBKEY_EXPONENT_SIZE, ctx.random()); + //t1 = Clock.getInstance().now(); if (BigInteger.ZERO.compareTo(k) == 0) { k = null; continue; @@ -139,39 +113,31 @@ class YKGenerator { BigInteger kPlus2 = k.add(_two); if (kPlus2.compareTo(CryptoConstants.elgp) > 0) k = null; } - long t2 = Clock.getInstance().now(); + //long t2 = Clock.getInstance().now(); y = CryptoConstants.elgg.modPow(k, CryptoConstants.elgp); BigInteger yk[] = new BigInteger[2]; yk[0] = y; yk[1] = k; - long diff = t2 - t0; - if (diff > 1000) { - if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to generate YK value for ElGamal (" + diff + "ms)"); - } + //long diff = t2 - t0; + //if (diff > 1000) { + // if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to generate YK value for ElGamal (" + diff + "ms)"); + //} return yk; } public static void main(String args[]) { - RandomSource.getInstance().nextBoolean(); // warm it up - try { - Thread.sleep(20 * 1000); - } catch (InterruptedException ie) { // nop - } - _log.debug("\n\n\n\nBegin test\n"); + System.out.println("\n\n\n\nBegin test\n"); long negTime = 0; for (int i = 0; i < 5; i++) { long startNeg = Clock.getInstance().now(); getNextYK(); long endNeg = Clock.getInstance().now(); + negTime += endNeg - startNeg; } - _log.debug("YK fetch time for 5 runs: " + negTime + " @ " + negTime / 5l + "ms each"); - try { - Thread.sleep(30 * 1000); - } catch (InterruptedException ie) { // nop - } + System.out.println("YK fetch time for 5 runs: " + negTime + " @ " + negTime / 5l + "ms each"); } private static class YKPrecalcRunner implements Runnable { @@ -186,15 +152,21 @@ class YKGenerator { public void run() { while (true) { int curSize = 0; - long start = Clock.getInstance().now(); + //long start = Clock.getInstance().now(); int startSize = getSize(); + // Adjust delay + if (startSize <= (_minSize / 2) && CHECK_DELAY > 1000) + CHECK_DELAY -= 1000; + else if (startSize > (_minSize * 2) && CHECK_DELAY < 60000) + CHECK_DELAY += 1000; curSize = startSize; - while (curSize < _minSize) { - while (curSize < _maxSize) { - long begin = Clock.getInstance().now(); - curSize = addValues(generateYK()); - long end = Clock.getInstance().now(); - if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalculated YK value in " + (end - begin) + "ms"); + if (curSize < _minSize) { + for (int i = curSize; i < _maxSize; i++) { + //long begin = Clock.getInstance().now(); + if (!addValues(generateYK())) + break; + //long end = Clock.getInstance().now(); + //if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalculated YK value in " + (end - begin) + "ms"); // for some relief... try { Thread.sleep(CALC_DELAY); @@ -202,14 +174,14 @@ class YKGenerator { } } } - long end = Clock.getInstance().now(); - int numCalc = curSize - startSize; - if (numCalc > 0) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Precalced " + numCalc + " to " + curSize + " in " - + (end - start - CALC_DELAY * numCalc) + "ms (not counting " - + (CALC_DELAY * numCalc) + "ms relief). now sleeping"); - } + //long end = Clock.getInstance().now(); + //int numCalc = curSize - startSize; + //if (numCalc > 0) { + // if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Precalced " + numCalc + " to " + curSize + " in " + // + (end - start - CALC_DELAY * numCalc) + "ms (not counting " + // + (CALC_DELAY * numCalc) + "ms relief). now sleeping"); + //} try { Thread.sleep(CHECK_DELAY); } catch (InterruptedException ie) { // nop @@ -217,4 +189,4 @@ class YKGenerator { } } } -} \ No newline at end of file +} diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 0743defde87cedd8b630347ce0a52d630c18f54d..bb1ffe55c0c8839b36f8ca833da10f2a664d5975 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -17,7 +17,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -42,6 +41,7 @@ import net.i2p.util.ByteCache; import net.i2p.util.OrderedProperties; import net.i2p.util.ReusableGZIPInputStream; import net.i2p.util.ReusableGZIPOutputStream; +import net.i2p.util.SecureFileOutputStream; /** * Defines some simple IO routines for dealing with marshalling data structures @@ -339,11 +339,12 @@ public class DataHelper { /** * Writes the props to the file, unsorted (unless props is an OrderedProperties) * Note that this does not escape the \r or \n that are unescaped in loadProps() above. + * As of 0.8.1, file will be mode 600. */ public static void storeProps(Properties props, File file) throws IOException { PrintWriter out = null; try { - out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))); + out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); out.println("# NOTE: This I2P config file must use UTF-8 encoding"); for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { String name = (String)iter.next(); diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java index 83579d11663d2d8f16cb412a079cbab2e24ac115..afd173c2454751219d6e28dbec32246935579c8b 100644 --- a/core/java/src/net/i2p/data/Hash.java +++ b/core/java/src/net/i2p/data/Hash.java @@ -31,6 +31,7 @@ public class Hash extends DataStructureImpl { public Hash() { } + /** @throws IllegalArgumentException if data is not 32 bytes (null is ok) */ public Hash(byte data[]) { setData(data); } @@ -39,7 +40,10 @@ public class Hash extends DataStructureImpl { return _data; } + /** @throws IllegalArgumentException if data is not 32 bytes (null is ok) */ public void setData(byte[] data) { + if (data != null && data.length != HASH_LENGTH) + throw new IllegalArgumentException("Hash must be 32 bytes"); _data = data; _stringified = null; _base64ed = null; diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index 9d199e57401add70fb9919555d489ee3e78918fc..1b4d6438f5e518caf134c9f4a9a61183f7aaf96e 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -14,8 +14,11 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.Iterator; +import java.util.Map; import java.util.Properties; +import net.i2p.util.OrderedProperties; + /** * Defines a method of communicating with a router * @@ -28,7 +31,7 @@ public class RouterAddress extends DataStructureImpl { private Properties _options; public RouterAddress() { - setCost(-1); + _cost = -1; } /** @@ -134,18 +137,27 @@ public class RouterAddress extends DataStructureImpl { return DataHelper.hashCode(_transportStyle); } + /** + * This is used on peers.jsp so sort options so it looks better. + * We don't just use OrderedProperties for _options because DataHelper.writeProperties() + * sorts also. + */ @Override public String toString() { - StringBuilder buf = new StringBuilder(64); + StringBuilder buf = new StringBuilder(128); buf.append("[RouterAddress: "); - buf.append("\n\tTransportStyle: ").append(getTransportStyle()); - buf.append("\n\tCost: ").append(getCost()); - buf.append("\n\tExpiration: ").append(getExpiration()); - buf.append("\n\tOptions: #: ").append(getOptions().size()); - for (Iterator iter = getOptions().keySet().iterator(); iter.hasNext();) { - String key = (String) iter.next(); - String val = getOptions().getProperty(key); - buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); + buf.append("\n\tTransportStyle: ").append(_transportStyle); + buf.append("\n\tCost: ").append(_cost); + buf.append("\n\tExpiration: ").append(_expiration); + if (_options != null) { + buf.append("\n\tOptions: #: ").append(_options.size()); + Properties p = new OrderedProperties(); + p.putAll(_options); + for (Map.Entry e : p.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); + buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); + } } buf.append("]"); return buf.toString(); diff --git a/core/java/src/net/i2p/data/i2cp/AbuseReason.java b/core/java/src/net/i2p/data/i2cp/AbuseReason.java index f631fa591198c67bbb827b9d47b3af7122bd4018..99388faf201866938cc9a244ef94a610372c7cb5 100644 --- a/core/java/src/net/i2p/data/i2cp/AbuseReason.java +++ b/core/java/src/net/i2p/data/i2cp/AbuseReason.java @@ -49,16 +49,16 @@ public class AbuseReason extends DataStructureImpl { @Override public boolean equals(Object object) { if ((object == null) || !(object instanceof AbuseReason)) return false; - return DataHelper.eq(getReason(), ((AbuseReason) object).getReason()); + return DataHelper.eq(_reason, ((AbuseReason) object).getReason()); } @Override public int hashCode() { - return DataHelper.hashCode(getReason()); + return DataHelper.hashCode(_reason); } @Override public String toString() { - return "[AbuseReason: " + getReason() + "]"; + return "[AbuseReason: " + _reason + "]"; } } diff --git a/core/java/src/net/i2p/data/i2cp/AbuseSeverity.java b/core/java/src/net/i2p/data/i2cp/AbuseSeverity.java index 69cba7d2470bd8db4f685843dcaedbccd11b7913..bc3b9bbc9769705a7b1a7ae218725c4a92dd42d9 100644 --- a/core/java/src/net/i2p/data/i2cp/AbuseSeverity.java +++ b/core/java/src/net/i2p/data/i2cp/AbuseSeverity.java @@ -28,7 +28,7 @@ public class AbuseSeverity extends DataStructureImpl { private int _severityId; public AbuseSeverity() { - setSeverity(-1); + _severityId = -1; } public int getSeverity() { @@ -56,11 +56,11 @@ public class AbuseSeverity extends DataStructureImpl { @Override public int hashCode() { - return getSeverity(); + return _severityId; } @Override public String toString() { - return "[AbuseSeverity: " + getSeverity() + "]"; + return "[AbuseSeverity: " + _severityId + "]"; } } diff --git a/core/java/src/net/i2p/data/i2cp/CreateSessionMessage.java b/core/java/src/net/i2p/data/i2cp/CreateSessionMessage.java index 1b8004ce00a61930801b81662bd49011471f0bbe..8590a554c061fe22ce162d60082bb552b709e7ee 100644 --- a/core/java/src/net/i2p/data/i2cp/CreateSessionMessage.java +++ b/core/java/src/net/i2p/data/i2cp/CreateSessionMessage.java @@ -27,11 +27,11 @@ public class CreateSessionMessage extends I2CPMessageImpl { private SessionConfig _sessionConfig; public CreateSessionMessage(SessionConfig config) { - setSessionConfig(config); + _sessionConfig = config; } public CreateSessionMessage() { - setSessionConfig(new SessionConfig()); + _sessionConfig = new SessionConfig(); } public SessionConfig getSessionConfig() { @@ -75,7 +75,7 @@ public class CreateSessionMessage extends I2CPMessageImpl { public boolean equals(Object object) { if ((object != null) && (object instanceof CreateSessionMessage)) { CreateSessionMessage msg = (CreateSessionMessage) object; - return DataHelper.eq(getSessionConfig(), msg.getSessionConfig()); + return DataHelper.eq(_sessionConfig, msg.getSessionConfig()); } return false; @@ -85,7 +85,7 @@ public class CreateSessionMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[CreateSessionMessage: "); - buf.append("\n\tConfig: ").append(getSessionConfig()); + buf.append("\n\tConfig: ").append(_sessionConfig); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java b/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java index 8b4db852f77cde845f902025f4aa02a825c7c70c..edb07ae41674e0b8898ed7e8f73d81f415a00793 100644 --- a/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java +++ b/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java @@ -69,7 +69,7 @@ public class DestroySessionMessage extends I2CPMessageImpl { public boolean equals(Object object) { if ((object != null) && (object instanceof DestroySessionMessage)) { DestroySessionMessage msg = (DestroySessionMessage) object; - return DataHelper.eq(getSessionId(), msg.getSessionId()); + return DataHelper.eq(_sessionId, msg.getSessionId()); } return false; @@ -86,7 +86,7 @@ public class DestroySessionMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[DestroySessionMessage: "); - buf.append("\n\tSessionId: ").append(getSessionId()); + buf.append("\n\tSessionId: ").append(_sessionId); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java index 739f7823ebb110b861e3cbc732a862cbacacd7ad..aaa35427b38ec56e6e69b5c3d9aed162cf34285c 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java @@ -41,7 +41,7 @@ public class I2CPMessageHandler { try { if (length < 0) throw new I2CPMessageException("Invalid message length specified"); int type = (int) DataHelper.readLong(in, 1); - I2CPMessage msg = createMessage(in, length, type); + I2CPMessage msg = createMessage(type); msg.readMessage(in, length, type); return msg; } catch (DataFormatException dfe) { @@ -53,8 +53,8 @@ public class I2CPMessageHandler { * Yes, this is fairly ugly, but its the only place it ever happens. * */ - private static I2CPMessage createMessage(InputStream in, int length, int type) throws IOException, - I2CPMessageException { + private static I2CPMessage createMessage(int type) throws IOException, + I2CPMessageException { switch (type) { case CreateLeaseSetMessage.MESSAGE_TYPE: return new CreateLeaseSetMessage(); @@ -101,6 +101,7 @@ public class I2CPMessageHandler { } } +/*** public static void main(String args[]) { try { I2CPMessage msg = readMessage(new FileInputStream(args[0])); @@ -109,4 +110,5 @@ public class I2CPMessageHandler { e.printStackTrace(); } } +***/ } diff --git a/core/java/src/net/i2p/data/i2cp/MessageId.java b/core/java/src/net/i2p/data/i2cp/MessageId.java index d1db7376137484e9f1da94fa38bd3548cae5632f..365dc6dbb7928685a634082f8377033d948b5154 100644 --- a/core/java/src/net/i2p/data/i2cp/MessageId.java +++ b/core/java/src/net/i2p/data/i2cp/MessageId.java @@ -27,10 +27,10 @@ public class MessageId extends DataStructureImpl { private long _messageId; public MessageId() { - setMessageId(-1); + _messageId = -1; } public MessageId(long id) { - setMessageId(id); + _messageId = id; } public long getMessageId() { @@ -58,11 +58,11 @@ public class MessageId extends DataStructureImpl { @Override public int hashCode() { - return (int)getMessageId(); + return (int)_messageId; } @Override public String toString() { - return "[MessageId: " + getMessageId() + "]"; + return "[MessageId: " + _messageId + "]"; } } diff --git a/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java b/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java index f06fe86f326cf2b2c945f3d2a44c4846020f55a1..d9739ee585f2b88cf7ae2041995c43f8d45c7faa 100644 --- a/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java +++ b/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java @@ -29,8 +29,8 @@ public class MessagePayloadMessage extends I2CPMessageImpl { private Payload _payload; public MessagePayloadMessage() { - setSessionId(-1); - setMessageId(-1); + _sessionId = -1; + _messageId = -1; } public long getSessionId() { @@ -113,7 +113,7 @@ public class MessagePayloadMessage extends I2CPMessageImpl { MessagePayloadMessage msg = (MessagePayloadMessage) object; return _sessionId == msg.getSessionId() && _messageId == msg.getMessageId() - && DataHelper.eq(getPayload(), msg.getPayload()); + && DataHelper.eq(_payload, msg.getPayload()); } return false; @@ -123,9 +123,9 @@ public class MessagePayloadMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[MessagePayloadMessage: "); - buf.append("\n\tSessionId: ").append(getSessionId()); - buf.append("\n\tMessageId: ").append(getMessageId()); - buf.append("\n\tPayload: ").append(getPayload()); + buf.append("\n\tSessionId: ").append(_sessionId); + buf.append("\n\tMessageId: ").append(_messageId); + buf.append("\n\tPayload: ").append(_payload); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java b/core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java index 67fde8134b9bdf0b034a831a017e9328307ed39c..933a80785a79f6bfb664ff3348bfe95a554f9d73 100644 --- a/core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java +++ b/core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java @@ -32,17 +32,19 @@ public class MessageStatusMessage extends I2CPMessageImpl { public final static int STATUS_AVAILABLE = 0; public final static int STATUS_SEND_ACCEPTED = 1; + /** unused */ public final static int STATUS_SEND_BEST_EFFORT_SUCCESS = 2; + /** unused */ public final static int STATUS_SEND_BEST_EFFORT_FAILURE = 3; public final static int STATUS_SEND_GUARANTEED_SUCCESS = 4; public final static int STATUS_SEND_GUARANTEED_FAILURE = 5; public MessageStatusMessage() { - setSessionId(-1); - setStatus(-1); - setMessageId(-1); - setSize(-1); - setNonce(-1); + _sessionId = -1; + _status = -1; + _messageId = -1; + _size = -1; + _nonce = -1; } public long getSessionId() { @@ -169,11 +171,11 @@ public class MessageStatusMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[MessageStatusMessage: "); - buf.append("\n\tSessionId: ").append(getSessionId()); - buf.append("\n\tNonce: ").append(getNonce()); - buf.append("\n\tMessageId: ").append(getMessageId()); - buf.append("\n\tStatus: ").append(getStatusString(getStatus())); - buf.append("\n\tSize: ").append(getSize()); + buf.append("\n\tSessionId: ").append(_sessionId); + buf.append("\n\tNonce: ").append(_nonce); + buf.append("\n\tMessageId: ").append(_messageId); + buf.append("\n\tStatus: ").append(getStatusString(_status)); + buf.append("\n\tSize: ").append(_size); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/ReceiveMessageBeginMessage.java b/core/java/src/net/i2p/data/i2cp/ReceiveMessageBeginMessage.java index 6ad5e2745ddbaeb02b2d75be2e695e9fa2142c42..15bd6779dac81f17ab43ffe9bff2fd0341ea3ce6 100644 --- a/core/java/src/net/i2p/data/i2cp/ReceiveMessageBeginMessage.java +++ b/core/java/src/net/i2p/data/i2cp/ReceiveMessageBeginMessage.java @@ -28,8 +28,8 @@ public class ReceiveMessageBeginMessage extends I2CPMessageImpl { private long _messageId; public ReceiveMessageBeginMessage() { - setSessionId(-1); - setMessageId(-1); + _sessionId = -1; + _messageId = -1; } public long getSessionId() { @@ -103,8 +103,8 @@ public class ReceiveMessageBeginMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[ReceiveMessageBeginMessage: "); - buf.append("\n\tSessionId: ").append(getSessionId()); - buf.append("\n\tMessageId: ").append(getMessageId()); + buf.append("\n\tSessionId: ").append(_sessionId); + buf.append("\n\tMessageId: ").append(_messageId); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/ReceiveMessageEndMessage.java b/core/java/src/net/i2p/data/i2cp/ReceiveMessageEndMessage.java index 79cf883822e76b9722946c6c33dd322d9e95662c..718fd8b73985083235b6170abecb988083d3a0ff 100644 --- a/core/java/src/net/i2p/data/i2cp/ReceiveMessageEndMessage.java +++ b/core/java/src/net/i2p/data/i2cp/ReceiveMessageEndMessage.java @@ -27,8 +27,8 @@ public class ReceiveMessageEndMessage extends I2CPMessageImpl { private long _messageId; public ReceiveMessageEndMessage() { - setSessionId(-1); - setMessageId(-1); + _sessionId = -1; + _messageId = -1; } public long getSessionId() { @@ -87,8 +87,8 @@ public class ReceiveMessageEndMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[ReceiveMessageEndMessage: "); - buf.append("\n\tSessionId: ").append(getSessionId()); - buf.append("\n\tMessageId: ").append(getMessageId()); + buf.append("\n\tSessionId: ").append(_sessionId); + buf.append("\n\tMessageId: ").append(_messageId); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java b/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java index 9400091bede49bd3c7fcef052182e009a0f3710a..336fb3ed5b1703a47314239deeff3f46d58dc3fc 100644 --- a/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java +++ b/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java @@ -81,8 +81,8 @@ public class ReconfigureSessionMessage extends I2CPMessageImpl { public boolean equals(Object object) { if ((object != null) && (object instanceof ReconfigureSessionMessage)) { ReconfigureSessionMessage msg = (ReconfigureSessionMessage) object; - return DataHelper.eq(getSessionId(), msg.getSessionId()) - && DataHelper.eq(getSessionConfig(), msg.getSessionConfig()); + return DataHelper.eq(_sessionId, msg.getSessionId()) + && DataHelper.eq(_sessionConfig, msg.getSessionConfig()); } return false; @@ -92,8 +92,8 @@ public class ReconfigureSessionMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[ReconfigureSessionMessage: "); - buf.append("\n\tSessionId: ").append(getSessionId()); - buf.append("\n\tSessionConfig: ").append(getSessionConfig()); + buf.append("\n\tSessionId: ").append(_sessionId); + buf.append("\n\tSessionConfig: ").append(_sessionConfig); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java index ef877afe5e2c188fbd2524745ee793071722b566..32241e833861de22d05ad4fa36b6ba09e98673c8 100644 --- a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java +++ b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java @@ -30,7 +30,7 @@ import net.i2p.data.TunnelId; public class RequestLeaseSetMessage extends I2CPMessageImpl { public final static int MESSAGE_TYPE = 21; private SessionId _sessionId; - private List _endpoints; + private List<TunnelEndpoint> _endpoints; private Date _end; public RequestLeaseSetMessage() { @@ -51,12 +51,12 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl { public Hash getRouter(int endpoint) { if ((endpoint < 0) || (_endpoints.size() < endpoint)) return null; - return ((TunnelEndpoint) _endpoints.get(endpoint)).getRouter(); + return _endpoints.get(endpoint).getRouter(); } public TunnelId getTunnelId(int endpoint) { if ((endpoint < 0) || (_endpoints.size() < endpoint)) return null; - return ((TunnelEndpoint) _endpoints.get(endpoint)).getTunnelId(); + return _endpoints.get(endpoint).getTunnelId(); } /** @deprecated unused - presumably he meant remove? */ @@ -159,8 +159,6 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl { private TunnelId _tunnelId; public TunnelEndpoint() { - _router = null; - _tunnelId = null; } public TunnelEndpoint(Hash router, TunnelId id) { diff --git a/core/java/src/net/i2p/data/i2cp/SessionConfig.java b/core/java/src/net/i2p/data/i2cp/SessionConfig.java index b96918a459d832f544545f929ceb22a95c9bab9f..6534e0dbbdeb2bd61c7ca18103653892e047c6ea 100644 --- a/core/java/src/net/i2p/data/i2cp/SessionConfig.java +++ b/core/java/src/net/i2p/data/i2cp/SessionConfig.java @@ -168,8 +168,8 @@ public class SessionConfig extends DataStructureImpl { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { - _log.debug("PubKey size for destination: " + _destination.getPublicKey().getData().length); - _log.debug("SigningKey size for destination: " + _destination.getSigningPublicKey().getData().length); + //_log.debug("PubKey size for destination: " + _destination.getPublicKey().getData().length); + //_log.debug("SigningKey size for destination: " + _destination.getSigningPublicKey().getData().length); _destination.writeBytes(out); DataHelper.writeProperties(out, _options, true); // UTF-8 DataHelper.writeDate(out, _creationDate); diff --git a/core/java/src/net/i2p/data/i2cp/SessionId.java b/core/java/src/net/i2p/data/i2cp/SessionId.java index f79589c42b25dcd6ba50abc72f98fbac03364bba..708ba9c46026a0b3ff8ed6ebc085047a7bfda6df 100644 --- a/core/java/src/net/i2p/data/i2cp/SessionId.java +++ b/core/java/src/net/i2p/data/i2cp/SessionId.java @@ -26,7 +26,7 @@ public class SessionId extends DataStructureImpl { private int _sessionId; public SessionId() { - setSessionId(-1); + _sessionId = -1; } public int getSessionId() { @@ -49,16 +49,16 @@ public class SessionId extends DataStructureImpl { @Override public boolean equals(Object obj) { if ((obj == null) || !(obj instanceof SessionId)) return false; - return getSessionId() == ((SessionId) obj).getSessionId(); + return _sessionId == ((SessionId) obj).getSessionId(); } @Override public int hashCode() { - return getSessionId(); + return _sessionId; } @Override public String toString() { - return "[SessionId: " + getSessionId() + "]"; + return "[SessionId: " + _sessionId + "]"; } } diff --git a/core/java/src/net/i2p/data/i2cp/SetDateMessage.java b/core/java/src/net/i2p/data/i2cp/SetDateMessage.java index f0abce1a51d57847e1caabf8540737ca214e9112..9f6633b6ddda1f1d87b6bea65be90d52a1cae6b1 100644 --- a/core/java/src/net/i2p/data/i2cp/SetDateMessage.java +++ b/core/java/src/net/i2p/data/i2cp/SetDateMessage.java @@ -28,7 +28,7 @@ public class SetDateMessage extends I2CPMessageImpl { public SetDateMessage() { super(); - setDate(new Date(Clock.getInstance().now())); + _date = new Date(Clock.getInstance().now()); } public Date getDate() { @@ -70,7 +70,7 @@ public class SetDateMessage extends I2CPMessageImpl { public boolean equals(Object object) { if ((object != null) && (object instanceof SetDateMessage)) { SetDateMessage msg = (SetDateMessage) object; - return DataHelper.eq(getDate(), msg.getDate()); + return DataHelper.eq(_date, msg.getDate()); } return false; @@ -80,7 +80,7 @@ public class SetDateMessage extends I2CPMessageImpl { public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[SetDateMessage"); - buf.append("\n\tDate: ").append(getDate()); + buf.append("\n\tDate: ").append(_date); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java index 3fec768a5857cf153a9bc7d0606019ffcd56d54a..5b3f25c1925103246a43e741f133965feb55d8bc 100644 --- a/core/java/src/net/i2p/util/EepGet.java +++ b/core/java/src/net/i2p/util/EepGet.java @@ -55,11 +55,11 @@ public class EepGet { protected long _bytesTransferred; protected long _bytesRemaining; protected int _currentAttempt; - private String _etag; - private String _lastModified; + protected String _etag; + protected String _lastModified; protected boolean _encodingChunked; protected boolean _notModified; - private String _contentType; + protected String _contentType; protected boolean _transferFailed; protected boolean _headersRead; protected boolean _aborted; @@ -537,6 +537,15 @@ public class EepGet { if (_redirects > 5) throw new IOException("Too many redirects: to " + _redirectLocation); if (_log.shouldLog(Log.INFO)) _log.info("Redirecting to " + _redirectLocation); + + // reset some important variables, we don't want to save the values from the redirect + _bytesRemaining = -1; + _redirectLocation = null; + _etag = null; + _lastModified = null; + _contentType = null; + _encodingChunked = false; + sendRequest(timeout); doFetch(timeout); return; diff --git a/core/java/src/net/i2p/util/EepHead.java b/core/java/src/net/i2p/util/EepHead.java index 938241616a86ac608940679af6e80e6fd1bcf419..c3a17bbea594b43607380a6cb7cb40a56ec8cd22 100644 --- a/core/java/src/net/i2p/util/EepHead.java +++ b/core/java/src/net/i2p/util/EepHead.java @@ -116,6 +116,7 @@ public class EepHead extends EepGet { else timeout.setInactivityTimeout(60*1000); + // Should we even follow redirects for HEAD? if (_redirectLocation != null) { //try { URL oldURL = new URL(_actualURL); @@ -143,6 +144,15 @@ public class EepHead extends EepGet { if (_redirects > 5) throw new IOException("Too many redirects: to " + _redirectLocation); if (_log.shouldLog(Log.INFO)) _log.info("Redirecting to " + _redirectLocation); + + // reset some important variables, we don't want to save the values from the redirect + _bytesRemaining = -1; + _redirectLocation = null; + _etag = null; + _lastModified = null; + _contentType = null; + _encodingChunked = false; + sendRequest(timeout); doFetch(timeout); return; diff --git a/core/java/src/net/i2p/util/LogConsoleBuffer.java b/core/java/src/net/i2p/util/LogConsoleBuffer.java index a9aab2625ba74f793336b5bb8fcff0ae1b674146..2fe2708490a96ec5353691e7b68a303f88d44f38 100644 --- a/core/java/src/net/i2p/util/LogConsoleBuffer.java +++ b/core/java/src/net/i2p/util/LogConsoleBuffer.java @@ -11,8 +11,8 @@ import net.i2p.I2PAppContext; */ public class LogConsoleBuffer { private I2PAppContext _context; - private final List _buffer; - private final List _critBuffer; + private final List<String> _buffer; + private final List<String> _critBuffer; public LogConsoleBuffer(I2PAppContext context) { _context = context; @@ -43,7 +43,7 @@ public class LogConsoleBuffer { * in the logs) * */ - public List getMostRecentMessages() { + public List<String> getMostRecentMessages() { synchronized (_buffer) { return new ArrayList(_buffer); } @@ -54,9 +54,9 @@ public class LogConsoleBuffer { * in the logs) * */ - public List getMostRecentCriticalMessages() { + public List<String> getMostRecentCriticalMessages() { synchronized (_critBuffer) { return new ArrayList(_critBuffer); } } -} \ No newline at end of file +} diff --git a/core/java/src/net/i2p/util/LogManager.java b/core/java/src/net/i2p/util/LogManager.java index 8bd344d653de3f2052562f2972b62627d765c6e0..d3ece13c560870fb8b6463075e4ae6fb33379662 100644 --- a/core/java/src/net/i2p/util/LogManager.java +++ b/core/java/src/net/i2p/util/LogManager.java @@ -156,7 +156,7 @@ public class LogManager { return rv; } - /** @deprecated unused */ + /** now used by ConfigLogingHelper */ public List<Log> getLogs() { return new ArrayList(_logs.values()); } @@ -415,15 +415,21 @@ public class LogManager { /** * Determine how many bytes are in the given formatted string (5m, 60g, 100k, etc) - * + * Size may be k, m, or g; a trailing b is ignored. Upper-case is allowed. + * Spaces between the number and letter is are allowed. + * The number may be in floating point. + * 4096 min, 2 GB max (returns int) */ - public int getFileSize(String size) { - int sz = -1; + public static int getFileSize(String size) { try { - String v = size; - char mod = size.toUpperCase().charAt(size.length() - 1); - if (!Character.isDigit(mod)) v = size.substring(0, size.length() - 1); - int val = Integer.parseInt(v); + String v = size.trim().toUpperCase(); + if (v.length() < 2) + return -1; + if (v.endsWith("B")) + v = v.substring(0, v.length() - 1); + char mod = v.charAt(v.length() - 1); + if (!Character.isDigit(mod)) v = v.substring(0, v.length() - 1); + double val = Double.parseDouble(v); switch (mod) { case 'K': val *= 1024; @@ -438,10 +444,11 @@ public class LogManager { // blah, noop break; } - return val; + if (val < 4096 || val > Integer.MAX_VALUE) + return -1; + return (int) val; } catch (Throwable t) { System.err.println("Error parsing config for filesize: [" + size + "]"); - t.printStackTrace(); return -1; } } diff --git a/core/java/src/net/i2p/util/LogWriter.java b/core/java/src/net/i2p/util/LogWriter.java index b15aa6cfbb723720958237b4ba5640b16cfb94dc..7dca7ba96dc75cdfe8d045491ee241baeda61aa7 100644 --- a/core/java/src/net/i2p/util/LogWriter.java +++ b/core/java/src/net/i2p/util/LogWriter.java @@ -11,7 +11,6 @@ package net.i2p.util; import java.io.BufferedWriter; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -158,7 +157,8 @@ class LogWriter implements Runnable { File parent = f.getParentFile(); if (parent != null) { if (!parent.exists()) { - boolean ok = parent.mkdirs(); + File sd = new SecureDirectory(parent.getAbsolutePath()); + boolean ok = sd.mkdirs(); if (!ok) { System.err.println("Unable to create the parent directory: " + parent.getAbsolutePath()); //System.exit(0); @@ -171,7 +171,7 @@ class LogWriter implements Runnable { } closeFile(); try { - _currentOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8")); + _currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8")); } catch (IOException ioe) { System.err.println("Error rotating into [" + f.getAbsolutePath() + "]" + ioe); } diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java index c6ac80a65ca63f6410b178098d2933928e2c3a7a..100ec47d736c8406b5d23a968384216fa88c000e 100644 --- a/core/java/src/net/i2p/util/RandomSource.java +++ b/core/java/src/net/i2p/util/RandomSource.java @@ -145,7 +145,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester { File f = new File(I2PAppContext.getGlobalContext().getConfigDir(), SEEDFILE); FileOutputStream fos = null; try { - fos = new FileOutputStream(f); + fos = new SecureFileOutputStream(f); fos.write(buf); } catch (IOException ioe) { // ignore diff --git a/core/java/src/net/i2p/util/SecureDirectory.java b/core/java/src/net/i2p/util/SecureDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..30d609e96fb70d50c1c206e8005ff0024623d92a --- /dev/null +++ b/core/java/src/net/i2p/util/SecureDirectory.java @@ -0,0 +1,74 @@ +package net.i2p.util; + +import java.io.File; + +/** + * Same as File but sets the file mode after mkdir() so it can + * be read and written by the owner only (i.e. 700 on linux) + * + * @since 0.8.1 + * @author zzz + */ +public class SecureDirectory extends File { + + private static final boolean canSetPerms = + (new VersionComparator()).compare(System.getProperty("java.version"), "1.6") >= 0; + private static final boolean isNotWindows = !System.getProperty("os.name").startsWith("Win"); + + public SecureDirectory(String pathname) { + super(pathname); + } + + public SecureDirectory(String parent, String child) { + super(parent, child); + } + + public SecureDirectory(File parent, String child) { + super(parent, child); + } + + /** + * Sets directory to mode 700 if the directory is created + */ + @Override + public boolean mkdir() { + boolean rv = super.mkdir(); + if (rv) + setPerms(); + return rv; + } + + /** + * Sets directory to mode 700 if the directory is created + * Does NOT change the mode of other created directories + */ + @Override + public boolean mkdirs() { + boolean rv = super.mkdirs(); + if (rv) + setPerms(); + return rv; + } + + /** + * Tries to set the permissions to 700, + * ignores errors + */ + private void setPerms() { + if (!canSetPerms) + return; + try { + setReadable(false, false); + setReadable(true, true); + setWritable(false, false); + setWritable(true, true); + if (isNotWindows) { + setExecutable(false, false); + setExecutable(true, true); + } + } catch (Throwable t) { + // NoSuchMethodException or NoSuchMethodError if we somehow got the + // version detection wrong or the JVM doesn't support it + } + } +} diff --git a/core/java/src/net/i2p/util/SecureFileOutputStream.java b/core/java/src/net/i2p/util/SecureFileOutputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..8a891344b1bdf6d69c8b410a8a81f2ddd2bc46eb --- /dev/null +++ b/core/java/src/net/i2p/util/SecureFileOutputStream.java @@ -0,0 +1,72 @@ +package net.i2p.util; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; + +/** + * Same as FileOutputStream but sets the file mode so it can only + * be read and written by the owner only (i.e. 600 on linux) + * + * @since 0.8.1 + * @author zzz + */ +public class SecureFileOutputStream extends FileOutputStream { + + private static final boolean canSetPerms = + (new VersionComparator()).compare(System.getProperty("java.version"), "1.6") >= 0; + + /** + * Sets output file to mode 600 + */ + public SecureFileOutputStream(String file) throws FileNotFoundException { + super(file); + setPerms(new File(file)); + } + + /** + * Sets output file to mode 600 only if append = false + * (otherwise it is presumed to be 600 already) + */ + public SecureFileOutputStream(String file, boolean append) throws FileNotFoundException { + super(file, append); + //if (!append) + setPerms(new File(file)); + } + + /** + * Sets output file to mode 600 + */ + public SecureFileOutputStream(File file) throws FileNotFoundException { + super(file); + setPerms(file); + } + + /** + * Sets output file to mode 600 only if append = false + * (otherwise it is presumed to be 600 already) + */ + public SecureFileOutputStream(File file, boolean append) throws FileNotFoundException { + super(file, append); + //if (!append) + setPerms(file); + } + + /** + * Tries to set the permissions to 600, + * ignores errors + */ + private static void setPerms(File f) { + if (!canSetPerms) + return; + try { + f.setReadable(false, false); + f.setReadable(true, true); + f.setWritable(false, false); + f.setWritable(true, true); + } catch (Throwable t) { + // NoSuchMethodException or NoSuchMethodError if we somehow got the + // version detection wrong or the JVM doesn't support it + } + } +} diff --git a/installer/resources/i2ptunnel.config b/installer/resources/i2ptunnel.config index 6f97a11700625f653e89af841c5837b357cfdb53..ecb45b2067b3b51c66fa398cd46b945de915eeb3 100644 --- a/installer/resources/i2ptunnel.config +++ b/installer/resources/i2ptunnel.config @@ -25,7 +25,7 @@ tunnel.0.startOnLoad=true # irc tunnel.1.name=IRC Proxy -tunnel.1.description=IRC proxy to access the anonymous IRC network +tunnel.1.description=IRC proxy to access anonymous IRC servers tunnel.1.type=ircclient tunnel.1.sharedClient=false tunnel.1.interface=127.0.0.1 diff --git a/installer/resources/start-i2p.txt b/installer/resources/start-i2p.txt index e20ed1e734c562cdb74a85f9cbde35593f8e825e..2e51ea48cce3c5ef0621215eb7cd8552ad6a4230 100644 --- a/installer/resources/start-i2p.txt +++ b/installer/resources/start-i2p.txt @@ -1,3 +1,8 @@ To start I2P, run: $INSTALL_PATH/i2prouter start + + +On non-x86, run: + +$INSTALL_PATH/runplain.sh diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java index 7ff81a2add6b44c19d12af2a63aed49c8baf2353..667d7fb18477372c529b95b37054cfadcabef810 100644 --- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java @@ -9,7 +9,9 @@ import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; /** - * Hold the tunnel request record, managing its encryption and decryption. + * Hold the tunnel request record, managing its ElGamal encryption and decryption. + * Iterative AES encryption/decryption is done elsewhere. + * * Cleartext: * <pre> * bytes 0-3: tunnel ID to receive messages as @@ -26,6 +28,12 @@ import net.i2p.data.SessionKey; * bytes 193-221: uninterpreted / random padding * </pre> * + * Encrypted: + * <pre> + * bytes 0-15: First 16 bytes of router hash + * bytes 16-527: ElGamal encrypted block (discarding zero bytes at elg[0] and elg[257]) + * </pre> + * */ public class BuildRequestRecord { private ByteArray _data; @@ -152,7 +160,7 @@ public class BuildRequestRecord { /** * Encrypt the record to the specified peer. The result is formatted as: <pre> - * bytes 0-15: SHA-256-128 of the current hop's identity (the toPeer parameter) + * bytes 0-15: truncated SHA-256 of the current hop's identity (the toPeer parameter) * bytes 15-527: ElGamal-2048 encrypted block * </pre> */ diff --git a/router/java/src/net/i2p/router/KeyManager.java b/router/java/src/net/i2p/router/KeyManager.java index 4d19228b7208c16a177770fcfb490cb7acf62cf2..fd0e85a9eb6f31cefeac96dbc5cff92e4bc22564 100644 --- a/router/java/src/net/i2p/router/KeyManager.java +++ b/router/java/src/net/i2p/router/KeyManager.java @@ -27,6 +27,8 @@ import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.util.Clock; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; /** * Maintain all of the key pairs for the router. @@ -142,7 +144,7 @@ public class KeyManager { } public void runJob() { String keyDir = getContext().getProperty(PROP_KEYDIR, DEFAULT_KEYDIR); - File dir = new File(getContext().getRouterDir(), keyDir); + File dir = new SecureDirectory(getContext().getRouterDir(), keyDir); if (!dir.exists()) dir.mkdirs(); if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite()) { @@ -219,7 +221,7 @@ public class KeyManager { FileInputStream in = null; try { if (exists) { - out = new FileOutputStream(keyFile); + out = new SecureFileOutputStream(keyFile); structure.writeBytes(out); return structure; } else { diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 0651e35d261465bb41c27462b9ba291c404f34da..a091cba13451c44d9d3c62b98a198a5f69e1a7ac 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -46,6 +46,7 @@ import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.I2PThread; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -305,6 +306,7 @@ public class Router { public void setHigherVersionSeen(boolean seen) { _higherVersionSeen = seen; } public long getWhenStarted() { return _started; } + /** wall clock uptime */ public long getUptime() { if ( (_context == null) || (_context.clock() == null) ) return 1; // racing on startup @@ -1053,11 +1055,12 @@ public class Router { * this does escape the \r or \n that are unescaped in DataHelper.loadProps(). * Note that the escaping of \r or \n was probably a mistake and should be taken out. * + * FIXME Synchronize!! */ public boolean saveConfig() { FileOutputStream fos = null; try { - fos = new FileOutputStream(_configFilename); + fos = new SecureFileOutputStream(_configFilename); StringBuilder buf = new StringBuilder(8*1024); buf.append("# NOTE: This I2P config file must use UTF-8 encoding\n"); synchronized (_config) { @@ -1541,7 +1544,7 @@ private static class PersistRouterInfoJob extends JobImpl { FileOutputStream fos = null; try { - fos = new FileOutputStream(infoFile); + fos = new SecureFileOutputStream(infoFile); info.writeBytes(fos); } catch (DataFormatException dfe) { _log.error("Error rebuilding the router information", dfe); diff --git a/router/java/src/net/i2p/router/RouterLaunch.java b/router/java/src/net/i2p/router/RouterLaunch.java index 13ee2f3cfbc5927a32e2ef829b178a87e72b746e..069ee3ec5f61260f46b04f4bb68432e65b3f1203 100644 --- a/router/java/src/net/i2p/router/RouterLaunch.java +++ b/router/java/src/net/i2p/router/RouterLaunch.java @@ -1,10 +1,11 @@ package net.i2p.router; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; +import net.i2p.util.SecureFileOutputStream; + /** * This is the class called by the runplain.sh script on linux * and the i2p.exe launcher on Windows. @@ -33,7 +34,7 @@ public class RouterLaunch { } System.setProperty(PROP_WRAPPER_LOG, logfile.getAbsolutePath()); try { - System.setOut(new PrintStream(new FileOutputStream(logfile, true))); + System.setOut(new PrintStream(new SecureFileOutputStream(logfile, true))); } catch (IOException ioe) { ioe.printStackTrace(); } diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index e5aa1b5abb4d2a9af84ff6f2d6a57c2178581ed5..f363bd27814dfd7600f4a85126ffa42e214c3cf6 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import net.i2p.client.I2PClient; import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.TransientSessionKeyManager; import net.i2p.data.Destination; @@ -76,11 +77,13 @@ public class ClientConnectionRunner { * This contains the last 10 MessageIds that have had their (non-ack) status * delivered to the client (so that we can be sure only to update when necessary) */ - private final List _alreadyProcessed; + private final List<MessageId> _alreadyProcessed; private ClientWriterRunner _writer; private Hash _destHashCache; /** are we, uh, dead */ private boolean _dead; + /** For outbound traffic. true if i2cp.messageReliability = "none"; @since 0.8.1 */ + private boolean _dontSendMSM; /** * Create a new runner against the given socket @@ -91,11 +94,9 @@ public class ClientConnectionRunner { _log = _context.logManager().getLog(ClientConnectionRunner.class); _manager = manager; _socket = socket; - _config = null; _messages = new ConcurrentHashMap(); _alreadyProcessed = new ArrayList(); _acceptedPending = new ConcurrentHashSet(); - _dead = false; } private static volatile int __id = 0; @@ -189,6 +190,9 @@ public class ClientConnectionRunner { if (_log.shouldLog(Log.DEBUG)) _log.debug("SessionEstablished called for destination " + _destHashCache.toBase64()); _config = config; + // This is the only option that is interpreted here, not at the tunnel manager + if (config.getOptions() != null) + _dontSendMSM = "none".equalsIgnoreCase(config.getOptions().getProperty(I2PClient.PROP_RELIABILITY)); // per-destination session key manager to prevent rather easy correlation if (_sessionKeyManager == null) _sessionKeyManager = new TransientSessionKeyManager(_context); @@ -197,10 +201,18 @@ public class ClientConnectionRunner { _manager.destinationEstablished(this); } + /** + * Send a notification to the client that their message (id specified) was + * delivered (or failed delivery) + * Note that this sends the Guaranteed status codes, even though we only support best effort. + * Doesn't do anything if i2cp.messageReliability = "none" + */ void updateMessageDeliveryStatus(MessageId id, boolean delivered) { - if (_dead) return; + if (_dead || _dontSendMSM) + return; _context.jobQueue().addJob(new MessageDeliveryStatusUpdate(id, delivered)); } + /** * called after a new leaseSet is granted by the client, the NetworkDb has been * updated. This takes care of all the LeaseRequestState stuff (including firing any jobs) @@ -254,7 +266,8 @@ public class ClientConnectionRunner { long expiration = 0; if (message instanceof SendMessageExpiresMessage) expiration = ((SendMessageExpiresMessage) message).getExpiration().getTime(); - _acceptedPending.add(id); + if (!_dontSendMSM) + _acceptedPending.add(id); if (_log.shouldLog(Log.DEBUG)) _log.debug("** Receiving message [" + id.getMessageId() + "] with payload of size [" @@ -276,9 +289,11 @@ public class ClientConnectionRunner { /** * Send a notification to the client that their message (id specified) was accepted * for delivery (but not necessarily delivered) - * + * Doesn't do anything if i2cp.messageReliability = "none" */ void ackSendMessage(MessageId id, long nonce) { + if (_dontSendMSM) + return; SessionId sid = _sessionId; if (sid == null) return; if (_log.shouldLog(Log.DEBUG)) @@ -517,12 +532,17 @@ public class ClientConnectionRunner { } public String getName() { return "Update Delivery Status"; } + + /** + * Note that this sends the Guaranteed status codes, even though we only support best effort. + */ public void runJob() { if (_dead) return; MessageStatusMessage msg = new MessageStatusMessage(); msg.setMessageId(_messageId.getMessageId()); msg.setSessionId(_sessionId.getSessionId()); + // has to be >= 0, it is initialized to -1 msg.setNonce(2); msg.setSize(0); if (_success) diff --git a/router/java/src/net/i2p/router/client/ClientListenerRunner.java b/router/java/src/net/i2p/router/client/ClientListenerRunner.java index 2c96fa4870645af110ee40367e52c1e779b9d2dd..7a7d448ea99d10bf1ec51ee2953142ca17c915f4 100644 --- a/router/java/src/net/i2p/router/client/ClientListenerRunner.java +++ b/router/java/src/net/i2p/router/client/ClientListenerRunner.java @@ -41,8 +41,6 @@ public class ClientListenerRunner implements Runnable { _log = _context.logManager().getLog(ClientListenerRunner.class); _manager = manager; _port = port; - _running = false; - _listening = false; String val = context.getProperty(BIND_ALL_INTERFACES); _bindAllInterfaces = Boolean.valueOf(val).booleanValue(); diff --git a/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java b/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java index e90d12f53599c7facc7c17b967c3ddc7b0c5cc5d..066d6cc354223c3ee1e53b56e31c78fc4f82e4ab 100644 --- a/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java +++ b/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java @@ -43,7 +43,6 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade { public ClientManagerFacadeImpl(RouterContext context) { _context = context; - _manager = null; _log.debug("Client manager facade created"); } diff --git a/router/java/src/net/i2p/router/client/LeaseRequestState.java b/router/java/src/net/i2p/router/client/LeaseRequestState.java index b4cf4415a4d7012515acf1b7bfe1c8040864e9f4..7e2a248ac948481129ceedd6890755f6f22b33ec 100644 --- a/router/java/src/net/i2p/router/client/LeaseRequestState.java +++ b/router/java/src/net/i2p/router/client/LeaseRequestState.java @@ -33,7 +33,6 @@ class LeaseRequestState { _onFailed = onFailed; _expiration = expiration; _requestedLeaseSet = requested; - _successful = false; } /** created lease set from client */ diff --git a/router/java/src/net/i2p/router/client/MessageReceivedJob.java b/router/java/src/net/i2p/router/client/MessageReceivedJob.java index d0c767828f296fd312d7668c66a16e762114390a..82744659abed01762717dd1e067e1a4fc87887c8 100644 --- a/router/java/src/net/i2p/router/client/MessageReceivedJob.java +++ b/router/java/src/net/i2p/router/client/MessageReceivedJob.java @@ -47,9 +47,6 @@ class MessageReceivedJob extends JobImpl { /** * Deliver notification to the client that the given message is available. - * This is synchronous and returns true if the notification was sent safely, - * otherwise it returns false - * */ public void messageAvailable(MessageId id, long size) { if (_log.shouldLog(Log.DEBUG)) @@ -59,6 +56,7 @@ class MessageReceivedJob extends JobImpl { msg.setMessageId(id.getMessageId()); msg.setSessionId(_runner.getSessionId().getSessionId()); msg.setSize(size); + // has to be >= 0, it is initialized to -1 msg.setNonce(1); msg.setStatus(MessageStatusMessage.STATUS_AVAILABLE); try { diff --git a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java index 51d3a304c907f4c5f91e3ff21b604d650f72b495..516a652d22b8b6381b5d94ec42c28193556649c1 100644 --- a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java +++ b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java @@ -44,9 +44,9 @@ class RequestLeaseSetJob extends JobImpl { _onCreate = onCreate; _onFail = onFail; _requestState = state; - ctx.statManager().createRateStat("client.requestLeaseSetSuccess", "How frequently the router requests successfully a new leaseSet?", "ClientMessages", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); - ctx.statManager().createRateStat("client.requestLeaseSetTimeout", "How frequently the router requests a new leaseSet but gets no reply?", "ClientMessages", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); - ctx.statManager().createRateStat("client.requestLeaseSetDropped", "How frequently the router requests a new leaseSet but the client drops?", "ClientMessages", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); + ctx.statManager().createRateStat("client.requestLeaseSetSuccess", "How frequently the router requests successfully a new leaseSet?", "ClientMessages", new long[] { 60*60*1000 }); + ctx.statManager().createRateStat("client.requestLeaseSetTimeout", "How frequently the router requests a new leaseSet but gets no reply?", "ClientMessages", new long[] { 60*60*1000 }); + ctx.statManager().createRateStat("client.requestLeaseSetDropped", "How frequently the router requests a new leaseSet but the client drops?", "ClientMessages", new long[] { 60*60*1000 }); } public String getName() { return "Request Lease Set"; } diff --git a/router/java/src/net/i2p/router/message/GarlicConfig.java b/router/java/src/net/i2p/router/message/GarlicConfig.java index 1c616f43cfb59be51e006109830ae3c559f006b8..b23d4ac9a34836a5599991d9ae8cfa75debfb1eb 100644 --- a/router/java/src/net/i2p/router/message/GarlicConfig.java +++ b/router/java/src/net/i2p/router/message/GarlicConfig.java @@ -37,17 +37,9 @@ public class GarlicConfig { private long _replyBlockExpiration; public GarlicConfig() { - _recipient = null; - _recipientPublicKey = null; - _cert = null; _id = -1; _expiration = -1; _cloveConfigs = new ArrayList(); - _instructions = null; - _requestAck = false; - _replyThroughRouter = null; - _replyInstructions = null; - _replyBlockCertificate = null; _replyBlockMessageId = -1; _replyBlockExpiration = -1; } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index 688b8546d286c37c7055e8fb6d1e5960f67a70c9..acede96edba33cce0456b6c7869464b2adf09368 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -194,10 +194,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { public void shutdown() { _initialized = false; - _kb = null; + // don't null out _kb, it can cause NPEs in concurrent operations + //_kb = null; if (_ds != null) _ds.stop(); - _ds = null; + // don't null out _ds, it can cause NPEs in concurrent operations + //_ds = null; _exploreKeys.clear(); // hope this doesn't cause an explosion, it shouldn't. // _exploreKeys = null; } @@ -750,6 +752,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } else if (upLongEnough && !routerInfo.isCurrent(ROUTER_INFO_EXPIRATION_SHORT)) { if (routerInfo.getAddresses().isEmpty()) return "Peer " + key.toBase64() + " published > 90m ago with no addresses"; + // This should cover the introducers case below too + // And even better, catches the case where the router is unreachable but knows no introducers + if (routerInfo.getCapabilities().indexOf(Router.CAPABILITY_UNREACHABLE) >= 0) + return "Peer " + key.toBase64() + " published > 90m ago and thinks it is unreachable"; RouterAddress ra = routerInfo.getTargetAddress("SSU"); if (ra != null) { // Introducers change often, introducee will ping introducer for 2 hours diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 77ea8c4573136b0c58b402927c110b209f846b97..0172dfcc6907a5b54923498cfcd4364877d587bc 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -29,6 +29,8 @@ import net.i2p.router.RouterContext; import net.i2p.router.networkdb.reseed.ReseedChecker; import net.i2p.util.I2PThread; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; /** * Write out keys to disk when we get them and periodically read ones we don't know @@ -288,7 +290,7 @@ class PersistentDataStore extends TransientDataStore { long dataPublishDate = getPublishDate(data); if (dbFile.lastModified() < dataPublishDate) { // our filesystem is out of date, lets replace it - fos = new FileOutputStream(dbFile); + fos = new SecureFileOutputStream(dbFile); try { data.writeBytes(fos); fos.close(); @@ -440,7 +442,7 @@ class PersistentDataStore extends TransientDataStore { private File getDbDir() throws IOException { - File f = new File(_context.getRouterDir(), _dbDir); + File f = new SecureDirectory(_context.getRouterDir(), _dbDir); if (!f.exists()) { boolean created = f.mkdirs(); if (!created) diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index a9c85cd74b5c3c4f47d97fb6e677833620af5c29..776d7eee46b4fa0445be32cb80f84b54cdc6748e 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -17,6 +17,8 @@ import net.i2p.router.RouterContext; import net.i2p.util.EepGet; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SSLEepGet; import net.i2p.util.Translate; @@ -260,11 +262,11 @@ public class Reseeder { private void writeSeed(String name, byte data[]) throws Exception { String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb"); - File netDbDir = new File(_context.getRouterDir(), dirName); + File netDbDir = new SecureDirectory(_context.getRouterDir(), dirName); if (!netDbDir.exists()) { boolean ok = netDbDir.mkdirs(); } - FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat")); + FileOutputStream fos = new SecureFileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat")); fos.write(data); fos.close(); } diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java index 606b4883cf288f4e7a905aa67df81a3fd7bf2456..610dfec4f8a26eb01f07a781b483bf180105a43b 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java +++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java @@ -3,7 +3,6 @@ package net.i2p.router.peermanager; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStream; @@ -19,6 +18,8 @@ import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.router.RouterContext; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; class ProfilePersistenceHelper { private Log _log; @@ -61,7 +62,7 @@ class ProfilePersistenceHelper { long before = _context.clock().now(); OutputStream fos = null; try { - fos = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(f))); + fos = new BufferedOutputStream(new GZIPOutputStream(new SecureFileOutputStream(f))); writeProfile(profile, fos); } catch (IOException ioe) { _log.error("Error writing profile to " + f); @@ -310,7 +311,7 @@ class ProfilePersistenceHelper { private File getProfileDir() { if (_profileDir == null) { String dir = _context.getProperty(PROP_PEER_PROFILE_DIR, DEFAULT_PEER_PROFILE_DIR); - _profileDir = new File(_context.getRouterDir(), dir); + _profileDir = new SecureDirectory(_context.getRouterDir(), dir); } return _profileDir; } diff --git a/router/java/src/net/i2p/router/startup/ClientAppConfig.java b/router/java/src/net/i2p/router/startup/ClientAppConfig.java index 982d4a298c4987bc5bdebe601f5f19c4fe4c0d36..33f4351fefcc06754d57bad86465cdb25b76bb89 100644 --- a/router/java/src/net/i2p/router/startup/ClientAppConfig.java +++ b/router/java/src/net/i2p/router/startup/ClientAppConfig.java @@ -11,6 +11,7 @@ import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; +import net.i2p.util.SecureFileOutputStream; /** @@ -191,7 +192,7 @@ public class ClientAppConfig { File cfgFile = configFile(ctx); FileOutputStream fos = null; try { - fos = new FileOutputStream(cfgFile); + fos = new SecureFileOutputStream(cfgFile); StringBuilder buf = new StringBuilder(2048); for(int i = 0; i < apps.size(); i++) { ClientAppConfig app = (ClientAppConfig) apps.get(i); diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java index 459cd0fb3ae2a8d07959a34cd208efb562cd1124..36b989bc7761609ab28b9bf3d83a3b994432d27a 100644 --- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java @@ -27,6 +27,7 @@ import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; public class CreateRouterInfoJob extends JobImpl { private static Log _log = new Log(CreateRouterInfoJob.class); @@ -80,12 +81,12 @@ public class CreateRouterInfoJob extends JobImpl { String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); File ifile = new File(getContext().getRouterDir(), infoFilename); - fos1 = new FileOutputStream(ifile); + fos1 = new SecureFileOutputStream(ifile); info.writeBytes(fos1); String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT); File kfile = new File(getContext().getRouterDir(), keyFilename); - fos2 = new FileOutputStream(kfile); + fos2 = new SecureFileOutputStream(kfile); privkey.writeBytes(fos2); signingPrivKey.writeBytes(fos2); pubkey.writeBytes(fos2); diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java index e3daf60ea44d7fec76f55b3b11eef195c44d1936..b417c32bbb3343ed72f34dae41f0d7399a32343c 100644 --- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java @@ -26,6 +26,7 @@ import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; /** * This used be called from StartAcceptingClientsJob but is now disabled. @@ -135,7 +136,7 @@ public class RebuildRouterInfoJob extends JobImpl { FileOutputStream fos = null; try { - fos = new FileOutputStream(infoFile); + fos = new SecureFileOutputStream(infoFile); info.writeBytes(fos); } catch (DataFormatException dfe) { _log.log(Log.CRIT, "Error rebuilding the router information", dfe); diff --git a/router/java/src/net/i2p/router/startup/WorkingDir.java b/router/java/src/net/i2p/router/startup/WorkingDir.java index 545239777fedf09d15a4706dacd5683a62083308..0b32c661f0ddce616cb77f0e184006375088afdf 100644 --- a/router/java/src/net/i2p/router/startup/WorkingDir.java +++ b/router/java/src/net/i2p/router/startup/WorkingDir.java @@ -11,6 +11,8 @@ import java.io.PrintWriter; import java.util.Properties; import net.i2p.data.DataHelper; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; /** * Get a working directory for i2p. @@ -64,19 +66,19 @@ public class WorkingDir { boolean isWindows = System.getProperty("os.name").startsWith("Win"); File dirf = null; if (dir != null) { - dirf = new File(dir); + dirf = new SecureDirectory(dir); } else { String home = System.getProperty("user.home"); if (isWindows) { String appdata = System.getenv("APPDATA"); if (appdata != null) home = appdata; - dirf = new File(home, WORKING_DIR_DEFAULT_WINDOWS); + dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_WINDOWS); } else { if (DAEMON_USER.equals(System.getProperty("user.name"))) - dirf = new File(home, WORKING_DIR_DEFAULT_DAEMON); + dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_DAEMON); else - dirf = new File(home, WORKING_DIR_DEFAULT); + dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT); } } @@ -143,7 +145,7 @@ public class WorkingDir { // this one must be after MIGRATE_BASE success &= migrateJettyXml(oldDirf, dirf); success &= migrateClientsConfig(oldDirf, dirf); - success &= copy(new File(oldDirf, "docs/news.xml"), new File(dirf, "docs")); + success &= copy(new File(oldDirf, "docs/news.xml"), new SecureDirectory(dirf, "docs")); // Report success or failure if (success) { @@ -197,7 +199,7 @@ public class WorkingDir { PrintWriter out = null; try { in = new FileInputStream(oldFile); - out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF-8"))); + out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(newFile), "UTF-8"))); out.println("# Modified by I2P User dir migration script"); String s = null; boolean isDaemon = DAEMON_USER.equals(System.getProperty("user.name")); @@ -240,7 +242,7 @@ public class WorkingDir { PrintWriter out = null; try { in = new FileInputStream(oldFile); - out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF-8"))); + out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(newFile), "UTF-8"))); String s = null; while ((s = DataHelper.readLine(in)) != null) { if (s.indexOf("./eepsite/") >= 0) { @@ -270,7 +272,7 @@ public class WorkingDir { * @param targetDir the directory to copy to, will be created if it doesn't exist * @return true for success OR if src does not exist */ - public static final boolean copy(File src, File targetDir) { + private static boolean copy(File src, File targetDir) { if (!src.exists()) return true; if (!targetDir.exists()) { @@ -280,7 +282,8 @@ public class WorkingDir { } System.err.println("Created " + targetDir.getPath()); } - File targetFile = new File(targetDir, src.getName()); + // SecureDirectory is a File so this works for non-directories too + File targetFile = new SecureDirectory(targetDir, src.getName()); if (!src.isDirectory()) return copyFile(src, targetFile); File children[] = src.listFiles(); @@ -305,10 +308,10 @@ public class WorkingDir { /** * @param src not a directory, must exist - * @param dst not a directory, will be overwritten if existing + * @param dst not a directory, will be overwritten if existing, will be mode 600 * @return true if it was copied successfully */ - public static boolean copyFile(File src, File dst) { + private static boolean copyFile(File src, File dst) { if (!src.exists()) return false; boolean rv = true; @@ -317,7 +320,7 @@ public class WorkingDir { FileOutputStream out = null; try { in = new FileInputStream(src); - out = new FileOutputStream(dst); + out = new SecureFileOutputStream(dst); int read = 0; while ( (read = in.read(buf)) != -1) diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java index cc0a63ba6cd9aa9339ad95f399a811b40b4e6ef0..85a89b3c6410b5eeaf471a37ace4812e5bc0fc58 100644 --- a/router/java/src/net/i2p/router/transport/GeoIP.java +++ b/router/java/src/net/i2p/router/transport/GeoIP.java @@ -98,23 +98,26 @@ public class GeoIP { public void run() { if (_lock.getAndSet(true)) return; - // clear the negative cache every few runs, to prevent it from getting too big - if (((++_lookupRunCount) % CLEAR) == 0) - _notFound.clear(); - Long[] search = _pendingSearch.toArray(new Long[_pendingSearch.size()]); - if (search.length <= 0) - return; - _pendingSearch.clear(); - Arrays.sort(search); - String[] countries = readGeoIPFile(search); - - for (int i = 0; i < countries.length; i++) { - if (countries[i] != null) - _IPToCountry.put(search[i], countries[i]); - else - _notFound.add(search[i]); + try { + // clear the negative cache every few runs, to prevent it from getting too big + if (((++_lookupRunCount) % CLEAR) == 0) + _notFound.clear(); + Long[] search = _pendingSearch.toArray(new Long[_pendingSearch.size()]); + if (search.length <= 0) + return; + _pendingSearch.clear(); + Arrays.sort(search); + String[] countries = readGeoIPFile(search); + + for (int i = 0; i < countries.length; i++) { + if (countries[i] != null) + _IPToCountry.put(search[i], countries[i]); + else + _notFound.add(search[i]); + } + } finally { + _lock.set(false); } - _lock.set(false); } } diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 87a8bc8b2b993803178f5907fe11bf6b5c30137f..243c76237bc4f283681405a5154125c2fd9bbb93 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -21,6 +21,7 @@ import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.Hash; import net.i2p.data.RouterAddress; @@ -36,7 +37,11 @@ import net.i2p.util.Translate; public class TransportManager implements TransportEventListener { private Log _log; - private List<Transport> _transports; + /** + * Converted from List to prevent concurrent modification exceptions. + * If we want more than one transport with the same style we will have to change this. + */ + private Map<String, Transport> _transports; private RouterContext _context; private UPnPManager _upnpManager; @@ -56,20 +61,20 @@ public class TransportManager implements TransportEventListener { _context.statManager().createRateStat("transport.bidFailSelf", "Could not attempt to bid on message, as it targeted ourselves", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("transport.bidFailNoTransports", "Could not attempt to bid on message, as none of the transports could attempt it", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("transport.bidFailAllTransports", "Could not attempt to bid on message, as all of the transports had failed", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); - _transports = new ArrayList(); + _transports = new ConcurrentHashMap(2); if (Boolean.valueOf(_context.getProperty(PROP_ENABLE_UPNP, "true")).booleanValue()) _upnpManager = new UPnPManager(context, this); } public void addTransport(Transport transport) { if (transport == null) return; - _transports.add(transport); + _transports.put(transport.getStyle(), transport); transport.setListener(this); } public void removeTransport(Transport transport) { if (transport == null) return; - _transports.remove(transport); + _transports.remove(transport.getStyle()); transport.setListener(null); } @@ -140,11 +145,10 @@ public class TransportManager implements TransportEventListener { _upnpManager.start(); configTransports(); _log.debug("Starting up the transport manager"); - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { RouterAddress addr = t.startListening(); if (_log.shouldLog(Log.DEBUG)) - _log.debug("Transport " + i + " (" + t.getStyle() + ") started"); + _log.debug("Transport " + t.getStyle() + " started"); } // kick UPnP - Do this to get the ports opened even before UDP registers an address transportAddressChanged(); @@ -161,19 +165,14 @@ public class TransportManager implements TransportEventListener { public void stopListening() { if (_upnpManager != null) _upnpManager.stop(); - for (int i = 0; i < _transports.size(); i++) { - _transports.get(i).stopListening(); + for (Transport t : _transports.values()) { + t.stopListening(); } _transports.clear(); } public Transport getTransport(String style) { - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); - if(style.equals(t.getStyle())) - return t; - } - return null; + return _transports.get(style); } int getTransportCount() { return _transports.size(); } @@ -189,16 +188,16 @@ public class TransportManager implements TransportEventListener { public int countActivePeers() { int peers = 0; - for (int i = 0; i < _transports.size(); i++) { - peers += _transports.get(i).countActivePeers(); + for (Transport t : _transports.values()) { + peers += t.countActivePeers(); } return peers; } public int countActiveSendPeers() { int peers = 0; - for (int i = 0; i < _transports.size(); i++) { - peers += _transports.get(i).countActiveSendPeers(); + for (Transport t : _transports.values()) { + peers += t.countActiveSendPeers(); } return peers; } @@ -210,8 +209,8 @@ public class TransportManager implements TransportEventListener { * @param pct percent of limit 0-100 */ public boolean haveOutboundCapacity(int pct) { - for (int i = 0; i < _transports.size(); i++) { - if (_transports.get(i).haveCapacity(pct)) + for (Transport t : _transports.values()) { + if (t.haveCapacity(pct)) return true; } return false; @@ -225,8 +224,8 @@ public class TransportManager implements TransportEventListener { public boolean haveHighOutboundCapacity() { if (_transports.isEmpty()) return false; - for (int i = 0; i < _transports.size(); i++) { - if (!_transports.get(i).haveCapacity(HIGH_CAPACITY_PCT)) + for (Transport t : _transports.values()) { + if (!t.haveCapacity(HIGH_CAPACITY_PCT)) return false; } return true; @@ -239,8 +238,8 @@ public class TransportManager implements TransportEventListener { * @param pct percent of limit 0-100 */ public boolean haveInboundCapacity(int pct) { - for (int i = 0; i < _transports.size(); i++) { - if (_transports.get(i).getCurrentAddress() != null && _transports.get(i).haveCapacity(pct)) + for (Transport t : _transports.values()) { + if (t.getCurrentAddress() != null && t.haveCapacity(pct)) return true; } return false; @@ -253,8 +252,8 @@ public class TransportManager implements TransportEventListener { */ public Vector getClockSkews() { Vector skews = new Vector(); - for (int i = 0; i < _transports.size(); i++) { - Vector tempSkews = _transports.get(i).getClockSkews(); + for (Transport t : _transports.values()) { + Vector tempSkews = t.getClockSkews(); if ((tempSkews == null) || (tempSkews.isEmpty())) continue; skews.addAll(tempSkews); } @@ -266,7 +265,7 @@ public class TransportManager implements TransportEventListener { /** @return the best status of any transport */ public short getReachabilityStatus() { short rv = CommSystemFacade.STATUS_UNKNOWN; - for (Transport t : _transports) { + for (Transport t : _transports.values()) { short s = t.getReachabilityStatus(); if (s < rv) rv = s; @@ -275,13 +274,12 @@ public class TransportManager implements TransportEventListener { } public void recheckReachability() { - for (int i = 0; i < _transports.size(); i++) - _transports.get(i).recheckReachability(); + for (Transport t : _transports.values()) + t.recheckReachability(); } public boolean isBacklogged(Hash dest) { - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { if (t.isBacklogged(dest)) return true; } @@ -289,8 +287,7 @@ public class TransportManager implements TransportEventListener { } public boolean isEstablished(Hash dest) { - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { if (t.isEstablished(dest)) return true; } @@ -303,8 +300,7 @@ public class TransportManager implements TransportEventListener { * This is NOT reset if the peer contacts us. */ public boolean wasUnreachable(Hash dest) { - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { if (!t.wasUnreachable(dest)) return false; } @@ -330,9 +326,9 @@ public class TransportManager implements TransportEventListener { public Map<String, RouterAddress> getAddresses() { Map<String, RouterAddress> rv = new HashMap(_transports.size()); // do this first since SSU may force a NTCP change - for (Transport t : _transports) + for (Transport t : _transports.values()) t.updateAddress(); - for (Transport t : _transports) { + for (Transport t : _transports.values()) { if (t.getCurrentAddress() != null) rv.put(t.getStyle(), t.getCurrentAddress()); } @@ -345,7 +341,7 @@ public class TransportManager implements TransportEventListener { */ private Map<String, Integer> getPorts() { Map<String, Integer> rv = new HashMap(_transports.size()); - for (Transport t : _transports) { + for (Transport t : _transports.values()) { int port = t.getRequestedPort(); if (t.getCurrentAddress() != null) { Properties opts = t.getCurrentAddress().getOptions(); @@ -386,8 +382,7 @@ public class TransportManager implements TransportEventListener { List<TransportBid> rv = new ArrayList(_transports.size()); Set failedTransports = msg.getFailedTransports(); - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { if (failedTransports.contains(t.getStyle())) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping transport " + t.getStyle() + " as it already failed"); @@ -415,8 +410,7 @@ public class TransportManager implements TransportEventListener { Hash peer = msg.getTarget().getIdentity().calculateHash(); Set failedTransports = msg.getFailedTransports(); TransportBid rv = null; - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { if (t.isUnreachable(peer)) { unreachableTransports++; // this keeps GetBids() from shitlisting for "no common transports" @@ -482,8 +476,7 @@ public class TransportManager implements TransportEventListener { public List getMostRecentErrorMessages() { List rv = new ArrayList(16); - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { rv.addAll(t.getMostRecentErrorMessages()); } return rv; @@ -491,8 +484,7 @@ public class TransportManager implements TransportEventListener { public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { TreeMap transports = new TreeMap(); - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { transports.put(t.getStyle(), t); } for (Iterator iter = transports.values().iterator(); iter.hasNext(); ) { @@ -506,8 +498,7 @@ public class TransportManager implements TransportEventListener { StringBuilder buf = new StringBuilder(4*1024); buf.append("<h3>").append(_("Router Transport Addresses")).append("</h3><pre>\n"); - for (int i = 0; i < _transports.size(); i++) { - Transport t = _transports.get(i); + for (Transport t : _transports.values()) { if (t.getCurrentAddress() != null) buf.append(t.getCurrentAddress()); else diff --git a/router/java/src/net/i2p/router/transport/UPnP.java b/router/java/src/net/i2p/router/transport/UPnP.java index aa2261cfc779527ce8ce4e578dc219b78ae8485e..d13a51cecdc97acdd9d9b1ff096492f250a0586d 100644 --- a/router/java/src/net/i2p/router/transport/UPnP.java +++ b/router/java/src/net/i2p/router/transport/UPnP.java @@ -581,6 +581,8 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis // If not in portsForwarded, it wasn't successful, try again if(portsForwarded.contains(port)) { // We have forwarded it, and it should be forwarded, cool. + // Big problem here, if firewall resets, we don't know it. + // Do we need to re-forward anyway? or poll the router? } else { // Needs forwarding if(portsToForwardNow == null) portsToForwardNow = new HashSet<ForwardPort>(); diff --git a/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java b/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java index f0201c4e8a9398c076f2884e84ada1baadffe264..c8b7c96309e414a9b14954f1bf090fd3e3b5aa19 100644 --- a/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java +++ b/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java @@ -4,7 +4,7 @@ package net.i2p.router.transport.udp; * Generic means of SACK/NACK transmission for partially or fully * received messages */ -public interface ACKBitfield { +interface ACKBitfield { /** what message is this partially ACKing? */ public long getMessageId(); /** how many fragments are covered in this bitfield? */ diff --git a/router/java/src/net/i2p/router/transport/udp/ACKSender.java b/router/java/src/net/i2p/router/transport/udp/ACKSender.java index 180232d6f24046c835b6fd0ab82f5c0c3099dfba..d6035766db1410fbf757b842ce3e52ce6bca2b02 100644 --- a/router/java/src/net/i2p/router/transport/udp/ACKSender.java +++ b/router/java/src/net/i2p/router/transport/udp/ACKSender.java @@ -15,7 +15,7 @@ import net.i2p.util.Log; * any outstanding ACKs. * */ -public class ACKSender implements Runnable { +class ACKSender implements Runnable { private RouterContext _context; private Log _log; private UDPTransport _transport; diff --git a/router/java/src/net/i2p/router/transport/udp/DummyThrottle.java b/router/java/src/net/i2p/router/transport/udp/DummyThrottle.java index 383f807c5965eba299b1a2fa970ea2c919f428ca..d2cb73934cca7533353949eda435bfdd217319ab 100644 --- a/router/java/src/net/i2p/router/transport/udp/DummyThrottle.java +++ b/router/java/src/net/i2p/router/transport/udp/DummyThrottle.java @@ -14,7 +14,7 @@ import net.i2p.router.OutNetMessage; * * @since 0.7.12 */ -public class DummyThrottle implements OutboundMessageFragments.ActiveThrottle { +class DummyThrottle implements OutboundMessageFragments.ActiveThrottle { public DummyThrottle() { } 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 2e1fea71f4816e6ee86b23b7da9d92b6e955b3ca..d19f929c275b22df983e31aef2655689e376140f 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -33,7 +33,7 @@ import net.i2p.util.SimpleTimer; * as well as to drop any failed establishment attempts. * */ -public class EstablishmentManager { +class EstablishmentManager { private final RouterContext _context; private final Log _log; private final UDPTransport _transport; @@ -316,6 +316,40 @@ public class EstablishmentManager { } } + /** + * Got a SessionDestroy on an established conn + */ + void receiveSessionDestroy(RemoteHostId from, PeerState state) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive session destroy (EST) from: " + from); + _transport.dropPeer(state, false, "received destroy message"); + } + + /** + * Got a SessionDestroy during outbound establish + */ + void receiveSessionDestroy(RemoteHostId from, OutboundEstablishState state) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive session destroy (OB) from: " + from); + _outboundStates.remove(from); + Hash peer = state.getRemoteIdentity().calculateHash(); + _transport.dropPeer(peer, false, "received destroy message"); + } + + /** + * Got a SessionDestroy - maybe after an inbound establish + */ + void receiveSessionDestroy(RemoteHostId from) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive session destroy (IB) from: " + from); + InboundEstablishState state = _inboundStates.remove(from); + if (state != null) { + Hash peer = state.getConfirmedIdentity().calculateHash(); + if (peer != null) + _transport.dropPeer(peer, false, "received destroy message"); + } + } + /** * A data packet arrived on an outbound connection being established, which * means its complete (yay!). This is a blocking call, more than I'd like... diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java index 829f62ee00321d6656ccfacfb78e4691c961d79b..ea40c616c87d20aac7023f095640f03c61027a25 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java @@ -20,7 +20,7 @@ import net.i2p.util.Log; * we are Bob. * */ -public class InboundEstablishState { +class InboundEstablishState { private final RouterContext _context; private final Log _log; // SessionRequest message @@ -218,6 +218,13 @@ public class InboundEstablishState { if (_receivedIdentity == null) _receivedIdentity = new byte[conf.readTotalFragmentNum()][]; int cur = conf.readCurrentFragmentNum(); + if (cur >= _receivedIdentity.length) { + // avoid AIOOBE + // should do more than this, but what? disconnect? + fail(); + packetReceived(); + return; + } if (_receivedIdentity[cur] == null) { byte fragment[] = new byte[conf.readCurrentFragmentSize()]; conf.readFragmentData(fragment, 0); diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java index ddd44b1436c4852a60b249adb7890442c7824e93..bc0ddc5f5e1fdc0fabec9c8a0d520028b05e2617 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java @@ -17,7 +17,7 @@ import net.i2p.util.Log; * basic line of defense here). * */ -public class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{ +class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{ private RouterContext _context; private Log _log; /** list of message IDs recently received, so we can ignore in flight dups */ diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java index 3159cad8734bbd4a4d3c126ceae7678d7e4df99b..9261dc70a383a681fbb236fdd59da2808169fe85 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java @@ -11,7 +11,7 @@ import net.i2p.util.Log; * Hold the raw data fragments of an inbound message * */ -public class InboundMessageState { +class InboundMessageState { private RouterContext _context; private Log _log; private long _messageId; diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index 6a2707f6080d1683f359d3d97de4fdc7bf19da95..326c43f15d1b4721d641b7a9db81df3e5d6619ad 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -20,7 +20,7 @@ import net.i2p.util.Log; /** * */ -public class IntroductionManager { +class IntroductionManager { private RouterContext _context; private Log _log; private UDPTransport _transport; @@ -96,7 +96,10 @@ public class IntroductionManager { int sz = peers.size(); start = start % sz; int found = 0; - long inactivityCutoff = _context.clock().now() - (UDPTransport.EXPIRE_TIMEOUT / 2); + long inactivityCutoff = _context.clock().now() - (UDPTransport.EXPIRE_TIMEOUT / 2); // 15 min + // if not too many to choose from, be less picky + if (sz <= howMany + 2) + inactivityCutoff -= UDPTransport.EXPIRE_TIMEOUT / 4; for (int i = 0; i < sz && found < howMany; i++) { PeerState cur = peers.get((start + i) % sz); RouterInfo ri = _context.netDb().lookupRouterInfoLocally(cur.getRemotePeer()); @@ -119,7 +122,11 @@ public class IntroductionManager { continue; } // Try to pick active peers... - if (cur.getLastReceiveTime() < inactivityCutoff || cur.getLastSendTime() < inactivityCutoff) { + // FIXME this is really strict and causes us to run out of introducers + // We have much less introducers than we used to have because routers don't offer + // if they are approaching max connections (see EstablishmentManager) + // FIXED, was ||, is this OK now? + if (cur.getLastReceiveTime() < inactivityCutoff && cur.getLastSendTime() < inactivityCutoff) { if (_log.shouldLog(Log.INFO)) _log.info("Peer is idle too long: " + cur); continue; @@ -135,6 +142,8 @@ public class IntroductionManager { found++; } + // FIXME failsafe if found == 0, relax inactivityCutoff and try again? + // Try to keep the connection up for two hours after we made anybody an introducer long pingCutoff = _context.clock().now() - (2 * 60 * 60 * 1000); inactivityCutoff = _context.clock().now() - (UDPTransport.EXPIRE_TIMEOUT / 4); @@ -156,7 +165,7 @@ public class IntroductionManager { * Not as elaborate as pickInbound() above. * Just a quick check to see how many volunteers we know, * which the Transport uses to see if we need more. - * @return number of peers that have volunteerd to introduce us + * @return number of peers that have volunteered to introduce us */ int introducerCount() { return _inbound.size(); diff --git a/router/java/src/net/i2p/router/transport/udp/MessageQueue.java b/router/java/src/net/i2p/router/transport/udp/MessageQueue.java index cc38ebbafec279bca630c800643bc32c2177e144..3d51055ca87a4da76f8d8e0679282285a1db8309 100644 --- a/router/java/src/net/i2p/router/transport/udp/MessageQueue.java +++ b/router/java/src/net/i2p/router/transport/udp/MessageQueue.java @@ -5,7 +5,7 @@ import net.i2p.router.OutNetMessage; /** * Base queue for messages not yet packetized */ -public interface MessageQueue { +interface MessageQueue { /** * Get the next message, blocking until one is found or the expiration * reached. diff --git a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java index 1a95c227fe6d42a29cfc3bf61a6f04e150692980..08b6088c4ffbd01295121adbed5559037ca30ca9 100644 --- a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java @@ -19,7 +19,7 @@ import net.i2p.util.Log; * parse 'em into I2NPMessages, and stick them on the * {@link net.i2p.router.InNetMessagePool} by way of the {@link UDPTransport}. */ -public class MessageReceiver { +class MessageReceiver { private RouterContext _context; private Log _log; private UDPTransport _transport; diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java index 6e3738f306d117fec298375bdbcbf93f415526c8..1e23a210c5bf33dad9ee794006eafa23512a534f 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -21,7 +21,7 @@ import net.i2p.util.Log; * they are Bob. * */ -public class OutboundEstablishState { +class OutboundEstablishState { private final RouterContext _context; private final Log _log; // SessionRequest message diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java index 2d4f272929e9e098433f131799b854cbe66f8b64..e25ab036915dcb28ccd19d7a26e59ef62d4dcb24 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java @@ -22,7 +22,7 @@ import net.i2p.util.Log; * message. * */ -public class OutboundMessageFragments { +class OutboundMessageFragments { private RouterContext _context; private Log _log; private UDPTransport _transport; diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java index 877ad10b174ab43c7591ec1856c8c906d383e5e2..a545cfb06258d7a3e2a4bb9e8d48015f8c708335 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java @@ -15,7 +15,7 @@ import net.i2p.util.Log; * Maintain the outbound fragmentation for resending * */ -public class OutboundMessageState { +class OutboundMessageState { private I2PAppContext _context; private Log _log; /** may be null if we are part of the establishment */ diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java b/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java index 841588ee4548491c2eb1551d79d2c3a3523f82a4..078105a4a7b1c00a471c9c758167879cdd1c7f37 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java @@ -12,7 +12,7 @@ import net.i2p.util.Log; * WARNING - UNUSED since 0.6.1.11 * */ -public class OutboundRefiller implements Runnable { +class OutboundRefiller implements Runnable { private RouterContext _context; private Log _log; private OutboundMessageFragments _fragments; diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 3ebbdd34382d3f3d0eb1adbdef49a8753834ac3c..4227aaad0e2985cecff7122382d1fa2a5ee34f62 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -4,7 +4,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collections; -import java.util.Date; import java.util.List; import net.i2p.I2PAppContext; @@ -22,8 +21,80 @@ import net.i2p.util.Log; * Big ol' class to do all our packet formatting. The UDPPackets generated are * fully authenticated, encrypted, and configured for delivery to the peer. * + * The following is from udp.html on the website: + +<p> +All UDP datagrams begin with a 16 byte MAC (Message Authentication Code) +and a 16 byte IV (Initialization Vector +followed by a variable +size payload encrypted with the appropriate key. The MAC used is +HMAC-MD5, truncated to 16 bytes, while the key is a full 32 byte AES256 +key. The specific construct of the MAC is the first 16 bytes from:</p> +<pre> + HMAC-MD5(payload || IV || (payloadLength ^ protocolVersion), macKey) +</pre> + +<p>The protocol version is currently 0.</p> + +<p>The payload itself is AES256/CBC encrypted with the IV and the +sessionKey, with replay prevention addressed within its body, +explained below. The payloadLength in the MAC is a 2 byte unsigned +integer in 2s complement.</p> + +<p>The protocolVersion is a 2 byte unsigned integer in 2s complement, +and currently set to 0. Peers using a different protocol version will +not be able to communicate with this peer, though earlier versions not +using this flag are.</p> + +<h2><a name="payload">Payload</a></h2> + +<p>Within the AES encrypted payload, there is a minimal common structure +to the various messages - a one byte flag and a four byte sending +timestamp (*seconds* since the unix epoch). The flag byte contains +the following bitfields:</p> +<pre> + bits 0-3: payload type + bit 4: rekey? + bit 5: extended options included + bits 6-7: reserved +</pre> + +<p>If the rekey flag is set, 64 bytes of keying material follow the +timestamp. If the extended options flag is set, a one byte option +size value is appended to, followed by that many extended option +bytes, which are currently uninterpreted.</p> + +<p>When rekeying, the first 32 bytes of the keying material is fed +into a SHA256 to produce the new MAC key, and the next 32 bytes are +fed into a SHA256 to produce the new session key, though the keys are +not immediately used. The other side should also reply with the +rekey flag set and that same keying material. Once both sides have +sent and received those values, the new keys should be used and the +previous keys discarded. It may be useful to keep the old keys +around briefly, to address packet loss and reordering.</p> + +<p>NOTE: Rekeying is currently unimplemented.</p> + +<pre> + Header: 37+ bytes + +----+----+----+----+----+----+----+----+ + | MAC | + | | + +----+----+----+----+----+----+----+----+ + | IV | + | | + +----+----+----+----+----+----+----+----+ + |flag| time | (optionally | + +----+----+----+----+----+ | + | this may have 64 byte keying material | + | and/or a one+N byte extended options) | + +---------------------------------------| +</pre> + + * + * */ -public class PacketBuilder { +class PacketBuilder { private I2PAppContext _context; private Log _log; private UDPTransport _transport; @@ -62,10 +133,16 @@ public class PacketBuilder { _context.statManager().createRateStat("udp.packetAuthTimeSlow", "How long it takes to encrypt and MAC a packet for sending (when its slow)", "udp", UDPTransport.RATES); } +/**** public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer) { return buildPacket(state, fragment, peer, null, null); } +****/ + /** + * This builds a data packet (PAYLOAD_TYPE_DATA). + * See the methods below for the other message types. + * * @param ackIdsRemaining list of messageIds (Long) that should be acked by this packet. * The list itself is passed by reference, and if a messageId is * transmitted and the sender does not want the ID to be included @@ -78,7 +155,9 @@ public class PacketBuilder { * included, it should be removed from the list. */ public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer, List<Long> ackIdsRemaining, List<ACKBitfield> partialACKsRemaining) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_DATA << 4)); + byte data[] = packet.getPacket().getData(); + int off = HEADER_SIZE; StringBuilder msg = null; boolean acksIncluded = false; @@ -90,19 +169,6 @@ public class PacketBuilder { msg.append("*"); } - byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int start = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - int off = start; - - // header - data[off] |= (UDPPacket.PAYLOAD_TYPE_DATA << 4); - // todo: add support for rekeying and extended options - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); - off += 4; - // ok, now for the body... // just always ask for an ACK for now... @@ -231,8 +297,10 @@ public class PacketBuilder { return packet; } - // We use this for keepalive purposes. - // It doesn't generate a reply, but that's ok. + /** + * We use this for keepalive purposes. + * It doesn't generate a reply, but that's ok. + */ public UDPPacket buildPing(PeerState peer) { return buildACK(peer, Collections.EMPTY_LIST); } @@ -242,11 +310,14 @@ public class PacketBuilder { /** * Build the ack packet. The list need not be sorted into full and partial; * this method will put all fulls before the partials in the outgoing packet. + * An ack packet is just a data packet with no data. * * @param ackBitfields list of ACKBitfield instances to either fully or partially ACK */ public UDPPacket buildACK(PeerState peer, List<ACKBitfield> ackBitfields) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_DATA << 4)); + byte data[] = packet.getPacket().getData(); + int off = HEADER_SIZE; StringBuilder msg = null; if (_log.shouldLog(Log.DEBUG)) { @@ -254,18 +325,6 @@ public class PacketBuilder { msg.append("building ACK packet to ").append(peer.getRemotePeer().toBase64().substring(0,6)); } - byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] |= (UDPPacket.PAYLOAD_TYPE_DATA << 4); - // todo: add support for rekeying and extended options - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); - off += 4; - int fullACKCount = 0; int partialACKCount = 0; for (int i = 0; i < ackBitfields.size(); i++) { @@ -352,7 +411,9 @@ public class PacketBuilder { * @return ready to send packet, or null if there was a problem */ public UDPPacket buildSessionCreatedPacket(InboundEstablishState state, int externalPort, SessionKey ourIntroKey) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(SESSION_CREATED_FLAG_BYTE); + byte data[] = packet.getPacket().getData(); + int off = HEADER_SIZE; InetAddress to = null; try { @@ -366,17 +427,6 @@ public class PacketBuilder { state.prepareSessionCreated(); - byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = SESSION_CREATED_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); - off += 4; - byte sentIP[] = state.getSentIP(); if ( (sentIP == null) || (sentIP.length <= 0) || ( (_transport != null) && (!_transport.isValid(sentIP)) ) ) { if (_log.shouldLog(Log.ERROR)) @@ -455,7 +505,10 @@ public class PacketBuilder { * @return ready to send packet, or null if there was a problem */ public UDPPacket buildSessionRequestPacket(OutboundEstablishState state) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(SESSION_REQUEST_FLAG_BYTE); + byte data[] = packet.getPacket().getData(); + int off = HEADER_SIZE; + byte toIP[] = state.getSentIP(); if ( (_transport !=null) && (!_transport.isValid(toIP)) ) { packet.release(); @@ -470,19 +523,8 @@ public class PacketBuilder { packet.release(); return null; } - - byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = SESSION_REQUEST_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending request with time = " + new Date(now*1000)); - off += 4; + _log.debug("Sending request"); // now for the body System.arraycopy(state.getSentX(), 0, data, off, state.getSentX().length); @@ -542,7 +584,10 @@ public class PacketBuilder { * @return ready to send packets, or null if there was a problem */ public UDPPacket buildSessionConfirmedPacket(OutboundEstablishState state, int fragmentNum, int numFragments, byte identity[]) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(SESSION_CONFIRMED_FLAG_BYTE); + byte data[] = packet.getPacket().getData(); + int off = HEADER_SIZE; + InetAddress to = null; try { to = InetAddress.getByAddress(state.getSentIP()); @@ -553,17 +598,6 @@ public class PacketBuilder { return null; } - byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = SESSION_CONFIRMED_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); - off += 4; - // now for the body data[off] |= fragmentNum << 4; data[off] |= (numFragments & 0xF); @@ -620,6 +654,36 @@ public class PacketBuilder { } + /** + * Build a destroy packet, which contains a header but no body. + * + * @since 0.8.1 + */ + public UDPPacket buildSessionDestroyPacket(PeerState peer) { + UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_SESSION_DESTROY << 4)); + byte data[] = packet.getPacket().getData(); + int off = HEADER_SIZE; + + StringBuilder msg = null; + if (_log.shouldLog(Log.DEBUG)) { + msg = new StringBuilder(128); + msg.append("building session destroy packet to ").append(peer.getRemotePeer().toBase64().substring(0,6)); + } + + // no body in this message + + if (msg != null) + _log.debug(msg.toString()); + + // pad up so we're on the encryption boundary + if ( (off % 16) != 0) + off += 16 - (off % 16); + packet.getPacket().setLength(off); + authenticate(packet, peer.getCurrentCipherKey(), peer.getCurrentMACKey()); + setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort()); + return packet; + } + /** * full flag info for a peerTest message. this can be fixed, * since we never rekey on test, and don't need any extended options @@ -636,19 +700,11 @@ public class PacketBuilder { return buildPeerTestFromAlice(toIP, toPort, toIntroKey, toIntroKey, nonce, aliceIntroKey); } public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toCipherKey, SessionKey toMACKey, long nonce, SessionKey aliceIntroKey) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_TEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = PEER_TEST_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); + int off = HEADER_SIZE; if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending peer test " + nonce + " to Bob with time = " + new Date(now*1000)); - off += 4; + _log.debug("Sending peer test " + nonce + " to Bob"); // now for the body DataHelper.toLong(data, off, 4, nonce); @@ -678,19 +734,11 @@ public class PacketBuilder { * @return ready to send packet, or null if there was a problem */ public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort, SessionKey aliceIntroKey, SessionKey charlieIntroKey, long nonce) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_TEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = PEER_TEST_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); + int off = HEADER_SIZE; if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending peer test " + nonce + " to Alice with time = " + new Date(now*1000)); - off += 4; + _log.debug("Sending peer test " + nonce + " to Alice"); // now for the body DataHelper.toLong(data, off, 4, nonce); @@ -725,19 +773,11 @@ public class PacketBuilder { public UDPPacket buildPeerTestToCharlie(InetAddress aliceIP, int alicePort, SessionKey aliceIntroKey, long nonce, InetAddress charlieIP, int charliePort, SessionKey charlieCipherKey, SessionKey charlieMACKey) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_TEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = PEER_TEST_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); + int off = HEADER_SIZE; if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending peer test " + nonce + " to Charlie with time = " + new Date(now*1000)); - off += 4; + _log.debug("Sending peer test " + nonce + " to Charlie"); // now for the body DataHelper.toLong(data, off, 4, nonce); @@ -770,19 +810,11 @@ public class PacketBuilder { * @return ready to send packet, or null if there was a problem */ public UDPPacket buildPeerTestToBob(InetAddress bobIP, int bobPort, InetAddress aliceIP, int alicePort, SessionKey aliceIntroKey, long nonce, SessionKey bobCipherKey, SessionKey bobMACKey) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_TEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = PEER_TEST_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); + int off = HEADER_SIZE; if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending peer test " + nonce + " to Bob with time = " + new Date(now*1000)); - off += 4; + _log.debug("Sending peer test " + nonce + " to Bob"); // now for the body DataHelper.toLong(data, off, 4, nonce); @@ -846,22 +878,15 @@ public class PacketBuilder { } public UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[], long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_RELAY_REQUEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; + int off = HEADER_SIZE; byte ourIP[] = getOurExplicitIP(); int ourPort = getOurExplicitPort(); - // header - data[off] = PEER_RELAY_REQUEST_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); if (_log.shouldLog(Log.INFO)) _log.info("Sending intro relay request to " + introHost + ":" + introPort); // + " regarding " + state.getRemoteIdentity().calculateHash().toBase64()); - off += 4; // now for the body DataHelper.toLong(data, off, 4, introTag); @@ -915,19 +940,11 @@ public class PacketBuilder { private static final byte PEER_RELAY_INTRO_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_INTRO << 4); UDPPacket buildRelayIntro(RemoteHostId alice, PeerState charlie, UDPPacketReader.RelayRequestReader request) { - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_RELAY_INTRO_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = PEER_RELAY_INTRO_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); + int off = HEADER_SIZE; if (_log.shouldLog(Log.INFO)) _log.info("Sending intro to " + charlie + " for " + alice); - off += 4; // now for the body byte ip[] = alice.getIP(); @@ -972,17 +989,9 @@ public class PacketBuilder { return null; } - UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket packet = buildPacketHeader(PEER_RELAY_RESPONSE_FLAG_BYTE); byte data[] = packet.getPacket().getData(); - Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; - - // header - data[off] = PEER_RELAY_RESPONSE_FLAG_BYTE; - off++; - long now = _context.clock().now() / 1000; - DataHelper.toLong(data, off, 4, now); - off += 4; + int off = HEADER_SIZE; if (_log.shouldLog(Log.INFO)) _log.info("Sending relay response to " + alice + " for " + charlie + " with alice's intro key " + aliceIntroKey.toBase64()); @@ -1019,11 +1028,13 @@ public class PacketBuilder { return packet; } + /** + * Sends an empty unauthenticated packet for hole punching + */ public UDPPacket buildHolePunch(UDPPacketReader reader) { UDPPacket packet = UDPPacket.acquire(_context, false); byte data[] = packet.getPacket().getData(); Arrays.fill(data, 0, data.length, (byte)0x0); - int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; int ipSize = reader.getRelayIntroReader().readIPSize(); byte ip[] = new byte[ipSize]; @@ -1052,7 +1063,34 @@ public class PacketBuilder { return packet; } - private void setTo(UDPPacket packet, InetAddress ip, int port) { + /** if no extended options or rekey data, which we don't support */ + private static final int HEADER_SIZE = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE + 1 + 4; + + /** + * Create a new packet and add the flag byte and the time stamp. + * Caller should add data starting at HEADER_SIZE. + * At this point, adding support for extended options and rekeying is unlikely, + * but if we do, we'll have to change this. + * + * @param flagByte contains type and flags + * @since 0.8.1 + */ + private UDPPacket buildPacketHeader(byte flagByte) { + UDPPacket packet = UDPPacket.acquire(_context, false); + byte data[] = packet.getPacket().getData(); + Arrays.fill(data, 0, data.length, (byte)0x0); + int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; + + // header + data[off] = flagByte; + off++; + long now = _context.clock().now() / 1000; + DataHelper.toLong(data, off, 4, now); + // todo: add support for rekeying and extended options + return packet; + } + + private static void setTo(UDPPacket packet, InetAddress ip, int port) { packet.getPacket().setAddress(ip); packet.getPacket().setPort(port); } diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java index 1a35c5d195741dc298b0f73968848080d86d285e..d130a4c8382c886b9c427bc0137f79d59f194fb1 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -19,7 +19,7 @@ import net.i2p.util.Log; * receiver's queue and pushing them as necessary. * */ -public class PacketHandler { +class PacketHandler { private RouterContext _context; private Log _log; private UDPTransport _transport; @@ -186,6 +186,10 @@ public class PacketHandler { } //} + /** + * Initial handling, called for every packet + * Find the state and call the correct receivePacket() variant + */ private void handlePacket(UDPPacketReader reader, UDPPacket packet) { if (packet == null) return; @@ -229,6 +233,10 @@ public class PacketHandler { } } + /** + * Established conn + * Decrypt and validate the packet then call handlePacket() + */ private void receivePacket(UDPPacketReader reader, UDPPacket packet, PeerState state) { _state = 17; boolean isValid = packet.validate(state.getCurrentMACKey()); @@ -280,6 +288,12 @@ public class PacketHandler { _state = 26; } + /** + * New conn or failed validation + * Decrypt and validate the packet then call handlePacket() + * + * @param peerType OUTBOUND_FALLBACK, INBOUND_FALLBACK, or NEW_PEER + */ private void receivePacket(UDPPacketReader reader, UDPPacket packet, short peerType) { _state = 27; boolean isValid = packet.validate(_transport.getIntroKey()); @@ -314,7 +328,11 @@ public class PacketHandler { private void receivePacket(UDPPacketReader reader, UDPPacket packet, InboundEstablishState state) { receivePacket(reader, packet, state, true); } + /** + * Inbound establishing conn + * Decrypt and validate the packet then call handlePacket() + * * @param allowFallback if it isn't valid for this establishment state, try as a non-establishment packet */ private void receivePacket(UDPPacketReader reader, UDPPacket packet, InboundEstablishState state, boolean allowFallback) { @@ -355,6 +373,10 @@ public class PacketHandler { } } + /** + * Outbound establishing conn + * Decrypt and validate the packet then call handlePacket() + */ private void receivePacket(UDPPacketReader reader, UDPPacket packet, OutboundEstablishState state) { _state = 35; if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) { @@ -406,6 +428,10 @@ public class PacketHandler { /** * Parse out the interesting bits and honor what it says + * + * @param state non-null if fully established + * @param outState non-null if outbound establishing in process + * @param inState unused always null */ private void handlePacket(UDPPacketReader reader, UDPPacket packet, PeerState state, OutboundEstablishState outState, InboundEstablishState inState) { _state = 43; @@ -525,6 +551,15 @@ public class PacketHandler { _establisher.receiveRelayResponse(from, reader); _context.statManager().addRateData("udp.receivePacketSize.relayResponse", packet.getPacket().getLength(), packet.getLifetime()); break; + case UDPPacket.PAYLOAD_TYPE_SESSION_DESTROY: + _state = 53; + if (outState != null) + _establisher.receiveSessionDestroy(from, outState); + else if (state != null) + _establisher.receiveSessionDestroy(from, state); + else + _establisher.receiveSessionDestroy(from); + break; default: _state = 52; if (_log.shouldLog(Log.WARN)) diff --git a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java index 271e83597c3435af227841eb072ac02067c48ba1..50b8d3ba780b50ce6035aa0e08d047e69b791ca1 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java @@ -9,7 +9,7 @@ import net.i2p.util.Log; * pool and toss 'em onto the outbound packet queue * */ -public class PacketPusher implements Runnable { +class PacketPusher implements Runnable { // private RouterContext _context; private Log _log; private OutboundMessageFragments _fragments; diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index 65fb1c0147caea54f2ea5300b29c108ce957967e..6c1dec3e4563de374ca5cb820c437fe98e0d6886 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -23,7 +23,7 @@ import net.i2p.util.ConcurrentHashSet; * Contain all of the state about a UDP connection to a peer. * */ -public class PeerState { +class PeerState { private RouterContext _context; private Log _log; /** diff --git a/router/java/src/net/i2p/router/transport/udp/TimedWeightedPriorityMessageQueue.java b/router/java/src/net/i2p/router/transport/udp/TimedWeightedPriorityMessageQueue.java index acd0bd0b532dde527703d37d13e99e25ca5d0844..9721f22cce5ec39bf6f58ab83fcfeebe5d7e2030 100644 --- a/router/java/src/net/i2p/router/transport/udp/TimedWeightedPriorityMessageQueue.java +++ b/router/java/src/net/i2p/router/transport/udp/TimedWeightedPriorityMessageQueue.java @@ -20,7 +20,7 @@ import net.i2p.util.Log; * See comments in DQAT.java and mtn history ca. 2006-02-19 * */ -public class TimedWeightedPriorityMessageQueue implements MessageQueue, OutboundMessageFragments.ActiveThrottle { +class TimedWeightedPriorityMessageQueue implements MessageQueue, OutboundMessageFragments.ActiveThrottle { private RouterContext _context; private Log _log; /** FIFO queue of messages in a particular priority */ diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java index ac9369d8eb7a059a7cf314d44c1bafece9d3e55c..fc2426700e5e52ba00832b75ee34615268544b82 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java @@ -11,7 +11,7 @@ import net.i2p.util.Log; * Coordinate the low level datagram socket, managing the UDPSender and * UDPReceiver */ -public class UDPEndpoint { +class UDPEndpoint { private RouterContext _context; private Log _log; private int _listenPort; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java index 5dc001e18f0efad6d5c77f138a19618960c27975..092431db3778d5d5118fbe3838e4cee61e4df55d 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java @@ -16,7 +16,7 @@ import net.i2p.util.Log; * of object instances to allow rapid reuse. * */ -public class UDPPacket { +class UDPPacket { private I2PAppContext _context; private static Log _log; private volatile DatagramPacket _packet; @@ -55,6 +55,7 @@ public class UDPPacket { public static final int IV_SIZE = 16; public static final int MAC_SIZE = 16; + /** Message types, 4 bits max */ public static final int PAYLOAD_TYPE_SESSION_REQUEST = 0; public static final int PAYLOAD_TYPE_SESSION_CREATED = 1; public static final int PAYLOAD_TYPE_SESSION_CONFIRMED = 2; @@ -63,13 +64,17 @@ public class UDPPacket { public static final int PAYLOAD_TYPE_RELAY_INTRO = 5; public static final int PAYLOAD_TYPE_DATA = 6; public static final int PAYLOAD_TYPE_TEST = 7; + /** @since 0.8.1 */ + public static final int PAYLOAD_TYPE_SESSION_DESTROY = 8; // various flag fields for use in the data packets public static final byte DATA_FLAG_EXPLICIT_ACK = (byte)(1 << 7); public static final byte DATA_FLAG_ACK_BITFIELDS = (1 << 6); + // unused public static final byte DATA_FLAG_ECN = (1 << 4); public static final byte DATA_FLAG_WANT_ACKS = (1 << 3); public static final byte DATA_FLAG_WANT_REPLY = (1 << 2); + // unused public static final byte DATA_FLAG_EXTENDED = (1 << 1); public static final byte BITFIELD_CONTINUATION = (byte)(1 << 7); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java index aaf0f721a62ca4537362c99101b1314a5ac1e60b..493d62771871491644e84fc7429b84fccbd9762b 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java @@ -15,7 +15,7 @@ import net.i2p.util.Log; * elements, grab the appropriate subreader. * */ -public class UDPPacketReader { +class UDPPacketReader { private I2PAppContext _context; private Log _log; private byte _message[]; @@ -125,6 +125,8 @@ public class UDPPacketReader { return "Relay request packet"; case UDPPacket.PAYLOAD_TYPE_RELAY_RESPONSE: return "Relay response packet"; + case UDPPacket.PAYLOAD_TYPE_SESSION_DESTROY: + return "Session destroyed packet"; default: return "Other packet type..."; } @@ -135,6 +137,8 @@ public class UDPPacketReader { buf.append(Base64.encode(_message, _payloadBeginOffset, _payloadLength)); } + /* ------- Begin Reader Classes ------- */ + /** Help read the SessionRequest payload */ public class SessionRequestReader { public static final int X_LENGTH = 256; @@ -755,7 +759,9 @@ public class UDPPacketReader { } } + /* ------- End Reader Classes ------- */ +/****** public static void main(String args[]) { I2PAppContext ctx = I2PAppContext.getGlobalContext(); try { @@ -781,4 +787,5 @@ public class UDPPacketReader { } } +*******/ } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index 88cb207791269b7c3b2784f797cb20ea7f6c7b80..0a1937db10d8c7c59e9c21da37b49536e6e7a563 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -19,7 +19,7 @@ import net.i2p.util.SimpleTimer; * from the queue ASAP by a {@link PacketHandler} * */ -public class UDPReceiver { +class UDPReceiver { private RouterContext _context; private Log _log; private DatagramSocket _socket; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPSender.java b/router/java/src/net/i2p/router/transport/udp/UDPSender.java index 745b50df4b57b35ae11686cdc2048e782b9cb91e..01d10ed1c5c17e4b6019fcc358a6c9c6f70219c5 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPSender.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPSender.java @@ -15,7 +15,7 @@ import net.i2p.util.Log; * Lowest level packet sender, pushes anything on its queue ASAP. * */ -public class UDPSender { +class UDPSender { private RouterContext _context; private Log _log; private DatagramSocket _socket; 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 5570b83893f2bc4b5449553e1929ee697bc5f769..70aaa1ecc9f10d6d2042b9523cbe02d7509c9c5e 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -838,7 +838,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (state != null) dropPeer(state, shouldShitlist, why); } - private void dropPeer(PeerState peer, boolean shouldShitlist, String why) { + + void dropPeer(PeerState peer, boolean shouldShitlist, String why) { if (_log.shouldLog(Log.WARN)) { long now = _context.clock().now(); StringBuilder buf = new StringBuilder(4096); @@ -945,7 +946,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // try to shift 'em around every 10 minutes or so if (_introducersSelectedOn < _context.clock().now() - 10*60*1000) { if (_log.shouldLog(Log.WARN)) - _log.warn("Our introducers are valid, but thy havent changed in a while, so lets rechoose"); + _log.warn("Our introducers are valid, but havent changed in a while, so lets rechoose"); return true; } else { if (_log.shouldLog(Log.INFO)) @@ -954,7 +955,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } } else { if (_log.shouldLog(Log.INFO)) - _log.info("Our introducers are not valid (" +valid + ")"); + _log.info("Need more introducers (have " +valid + " need " + PUBLIC_RELAY_COUNT + ')'); return true; } } else { @@ -1017,7 +1018,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** minimum active peers to maintain IP detection, etc. */ private static final int MIN_PEERS = 3; /** minimum peers volunteering to be introducers if we need that */ - private static final int MIN_INTRODUCER_POOL = 4; + private static final int MIN_INTRODUCER_POOL = 5; public TransportBid bid(RouterInfo toAddress, long dataSize) { if (dataSize > OutboundMessageState.MAX_MSG_SIZE) { @@ -1233,7 +1234,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _introducersSelectedOn = _context.clock().now(); introducersIncluded = true; } else { + // FIXME // maybe we should fail to publish an address at all in this case? + // YES that would be better if (_log.shouldLog(Log.WARN)) _log.warn("Need introducers but we don't know any"); } @@ -1331,13 +1334,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority switch (status) { case CommSystemFacade.STATUS_REJECT_UNSOLICITED: case CommSystemFacade.STATUS_DIFFERENT: - if (_log.shouldLog(Log.INFO)) - _log.info("Require introducers, because our status is " + status); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Require introducers, because our status is " + status); return true; default: if (!allowDirectUDP()) { - if (_log.shouldLog(Log.INFO)) - _log.info("Require introducers, because we do not allow direct UDP connections"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Require introducers, because we do not allow direct UDP connections"); return true; } return false; diff --git a/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java b/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java index fddccd52f308cfbcbddbd61a3e4c44cb428b4037..1194cc103c33b9c328ad8779b915afcea065228b 100644 --- a/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java @@ -24,7 +24,7 @@ public class BuildMessageProcessor { public BuildMessageProcessor(I2PAppContext ctx) { _filter = new DecayingHashSet(ctx, 60*1000, 32, "TunnelBMP"); - ctx.statManager().createRateStat("tunnel.buildRequestDup", "How frequently we get dup build request messages", "Tunnels", new long[] { 60*1000, 10*60*1000 }); + ctx.statManager().createRateStat("tunnel.buildRequestDup", "How frequently we get dup build request messages", "Tunnels", new long[] { 60*60*1000 }); } /** * Decrypt the record targetting us, encrypting all of the other records with the included diff --git a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java index 7376a5046692207b666f6edccbe3d70c34c9af9f..6d467016a664636e72c84f61a1335ce24da766f3 100644 --- a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java @@ -15,17 +15,19 @@ import net.i2p.util.Log; * */ public class InboundEndpointProcessor { - private RouterContext _context; - private Log _log; - private TunnelCreatorConfig _config; - private IVValidator _validator; + private final RouterContext _context; + private final Log _log; + private final TunnelCreatorConfig _config; + private final IVValidator _validator; static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION; private static final ByteCache _cache = ByteCache.getInstance(128, HopProcessor.IV_LENGTH); + /** @deprecated unused */ public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg) { this(ctx, cfg, DummyValidator.getInstance()); } + public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg, IVValidator validator) { _context = ctx; _log = ctx.logManager().getLog(InboundEndpointProcessor.class); @@ -84,6 +86,9 @@ public class InboundEndpointProcessor { return true; } + /** + * Iteratively undo the crypto that the various layers in the tunnel added. + */ private void decrypt(RouterContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) { Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class); ByteArray ba = _cache.acquire(); diff --git a/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java b/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java index 36593db0969f2725a542f66f502ad16c0e56e79c..bd912008098b31132064f14f53896ec056627fc5 100644 --- a/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java @@ -14,9 +14,9 @@ import net.i2p.util.Log; * */ public class OutboundGatewayProcessor { - private I2PAppContext _context; - private Log _log; - private TunnelCreatorConfig _config; + private final I2PAppContext _context; + private final Log _log; + private final TunnelCreatorConfig _config; static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION; private static final ByteCache _cache = ByteCache.getInstance(128, HopProcessor.IV_LENGTH); @@ -54,10 +54,8 @@ public class OutboundGatewayProcessor { } /** - * Undo the crypto that the various layers in the tunnel added. This is used - * by both the outbound gateway (preemptively undoing the crypto peers will add) - * and by the inbound endpoint. - * + * Iteratively undo the crypto that the various layers in the tunnel added. This is used + * by the outbound gateway (preemptively undoing the crypto peers will add). */ private void decrypt(I2PAppContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) { Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class); @@ -73,6 +71,11 @@ public class OutboundGatewayProcessor { _cache.release(ba); } + /** + * Undo the crypto for a single hop. This is used + * by both the outbound gateway (preemptively undoing the crypto peers will add) + * and by the inbound endpoint. + */ static void decrypt(I2PAppContext ctx, byte iv[], byte orig[], int offset, int length, byte cur[], HopConfig config) { // update the IV for the previous (next?) hop ctx.aes().decryptBlock(orig, offset, config.getIVKey(), orig, offset); diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java index 7291c8706a3207f7b07fda16c4b213874e4ac25f..3ec567098a91de813dde6e564b73eab7e797564b 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java @@ -60,7 +60,6 @@ public class TunnelGateway { _preprocessor = preprocessor; _sender = sender; _receiver = receiver; - _messagesSent = 0; _flushFrequency = 500; _delayedFlush = new DelayedFlush(); _lastFlush = _context.clock().now(); @@ -128,8 +127,8 @@ public class TunnelGateway { FlushTimer.getInstance().addEvent(_delayedFlush, delayAmount); } _context.statManager().addRateData("tunnel.lockedGatewayAdd", afterAdded-beforeLock, remaining); - long complete = System.currentTimeMillis(); - if (_log.shouldLog(Log.DEBUG)) + if (_log.shouldLog(Log.DEBUG)) { + long complete = System.currentTimeMillis(); _log.debug("Time to add the message " + msg.getUniqueId() + ": " + (complete-startAdd) + " delayed? " + delayedFlush + " remaining: " + remaining + " prepare: " + (beforeLock-startAdd) @@ -137,6 +136,7 @@ public class TunnelGateway { + " preprocess: " + (afterPreprocess-afterAdded) + " expire: " + (afterExpire-afterPreprocess) + " queue flush: " + (complete-afterExpire)); + } } public int getMessagesSent() { return _messagesSent; } @@ -202,10 +202,7 @@ public class TunnelGateway { _messageId = message.getUniqueId(); _expiration = message.getMessageExpiration(); _remaining = message.toByteArray(); - _offset = 0; - _fragmentNumber = 0; _created = now; - _messageIds = null; } /** may be null */ public Hash getToRouter() { return _toRouter; } diff --git a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java index a9199b690e76d7ce89b4b5f27cceb1ae3e3e1171..f4042feee9f30014416dc1f96aa1f1f47e432ba2 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import net.i2p.data.Hash; import net.i2p.router.RouterContext; import net.i2p.router.TunnelPoolSettings; @@ -14,7 +15,7 @@ import net.i2p.router.TunnelPoolSettings; * */ class ClientPeerSelector extends TunnelPeerSelector { - public List selectPeers(RouterContext ctx, TunnelPoolSettings settings) { + public List<Hash> selectPeers(RouterContext ctx, TunnelPoolSettings settings) { int length = getLength(ctx, settings); if (length < 0) return null; @@ -31,7 +32,7 @@ class ClientPeerSelector extends TunnelPeerSelector { ctx.profileOrganizer().selectFastPeers(length, exclude, matches, settings.getIPRestriction()); matches.remove(ctx.routerHash()); - ArrayList rv = new ArrayList(matches); + ArrayList<Hash> rv = new ArrayList(matches); if (rv.size() > 1) orderPeers(rv, settings.getRandomKey()); if (settings.isInbound()) diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java index 073088fb304f566687adce31106d3e7f1c7ef76d..503504cac56cf5dbb4f79f6f8de625e04b2a67d4 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import net.i2p.data.Hash; import net.i2p.router.RouterContext; import net.i2p.router.TunnelPoolSettings; import net.i2p.stat.Rate; @@ -17,7 +18,7 @@ import net.i2p.util.Log; * */ class ExploratoryPeerSelector extends TunnelPeerSelector { - public List selectPeers(RouterContext ctx, TunnelPoolSettings settings) { + public List<Hash> selectPeers(RouterContext ctx, TunnelPoolSettings settings) { Log l = ctx.logManager().getLog(getClass()); int length = getLength(ctx, settings); if (length < 0) { @@ -33,7 +34,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { return rv; } - Set exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory()); + Set<Hash> exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory()); exclude.add(ctx.routerHash()); // Don't use ff peers for exploratory tunnels to lessen exposure to netDb searches and stores // Hmm if they don't get explored they don't get a speed/capacity rating @@ -56,7 +57,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { l.debug("profileOrganizer.selectNotFailing(" + length + ") found " + matches); matches.remove(ctx.routerHash()); - ArrayList rv = new ArrayList(matches); + ArrayList<Hash> rv = new ArrayList(matches); if (rv.size() > 1) orderPeers(rv, settings.getRandomKey()); if (settings.isInbound()) @@ -67,7 +68,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { } private static final int MIN_NONFAILING_PCT = 25; - private boolean shouldPickHighCap(RouterContext ctx) { + private static boolean shouldPickHighCap(RouterContext ctx) { if (Boolean.valueOf(ctx.getProperty("router.exploreHighCapacity", "false")).booleanValue()) return true; // no need to explore too wildly at first @@ -86,9 +87,9 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { failPct = 100 - MIN_NONFAILING_PCT; } else { failPct = getExploratoryFailPercentage(ctx); - Log l = ctx.logManager().getLog(getClass()); - if (l.shouldLog(Log.DEBUG)) - l.debug("Normalized Fail pct: " + failPct); + //Log l = ctx.logManager().getLog(getClass()); + //if (l.shouldLog(Log.DEBUG)) + // l.debug("Normalized Fail pct: " + failPct); // always try a little, this helps keep the failPct stat accurate too if (failPct > 100 - MIN_NONFAILING_PCT) failPct = 100 - MIN_NONFAILING_PCT; @@ -96,21 +97,23 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { return (failPct >= ctx.random().nextInt(100)); } - // We should really use the difference between the exploratory fail rate - // and the high capacity fail rate - but we don't have a stat for high cap, - // so use the fast (== client) fail rate, it should be close - // if the expl. and client tunnel lengths aren't too different. - // So calculate the difference between the exploratory fail rate - // and the client fail rate, normalized to 100: - // 100 * ((Efail - Cfail) / (100 - Cfail)) - // Even this isn't the "true" rate for the NonFailingPeers pool, since we - // are often building exploratory tunnels using the HighCapacity pool. - private int getExploratoryFailPercentage(RouterContext ctx) { + /** + * We should really use the difference between the exploratory fail rate + * and the high capacity fail rate - but we don't have a stat for high cap, + * so use the fast (== client) fail rate, it should be close + * if the expl. and client tunnel lengths aren't too different. + * So calculate the difference between the exploratory fail rate + * and the client fail rate, normalized to 100: + * 100 * ((Efail - Cfail) / (100 - Cfail)) + * Even this isn't the "true" rate for the NonFailingPeers pool, since we + * are often building exploratory tunnels using the HighCapacity pool. + */ + private static int getExploratoryFailPercentage(RouterContext ctx) { int c = getFailPercentage(ctx, "Client"); int e = getFailPercentage(ctx, "Exploratory"); - Log l = ctx.logManager().getLog(getClass()); - if (l.shouldLog(Log.DEBUG)) - l.debug("Client, Expl. Fail pct: " + c + ", " + e); + //Log l = ctx.logManager().getLog(getClass()); + //if (l.shouldLog(Log.DEBUG)) + // l.debug("Client, Expl. Fail pct: " + c + ", " + e); if (e <= c || e <= 25) // doing very well (unlikely) return 0; if (c >= 90) // doing very badly @@ -118,7 +121,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { return (100 * (e-c)) / (100-c); } - private int getFailPercentage(RouterContext ctx, String t) { + private static int getFailPercentage(RouterContext ctx, String t) { String pfx = "tunnel.build" + t; int timeout = getEvents(ctx, pfx + "Expire", 10*60*1000); int reject = getEvents(ctx, pfx + "Reject", 10*60*1000); @@ -129,8 +132,8 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { return (int)(100 * pct); } - // Use current + last to get more recent and smoother data - private int getEvents(RouterContext ctx, String stat, long period) { + /** Use current + last to get more recent and smoother data */ + private static int getEvents(RouterContext ctx, String stat, long period) { RateStat rs = ctx.statManager().getRate(stat); if (rs == null) return 0; diff --git a/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java b/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java index afe149fc65254cf645892aca6cc13555cc7d0e5a..cbe7948150a8897b3b5b26c891ac95504f5d096d 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java +++ b/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java @@ -12,7 +12,7 @@ import net.i2p.util.Log; /** * */ -public class PooledTunnelCreatorConfig extends TunnelCreatorConfig { +class PooledTunnelCreatorConfig extends TunnelCreatorConfig { private TunnelPool _pool; private TestJob _testJob; // private Job _expireJob; 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 bb22ab0e448fe00a2c69fcb0e529130646d53f82..083e2501ae05f75dd087e6da942d1f4298be1f91 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -21,10 +21,13 @@ import net.i2p.router.TunnelPoolSettings; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.networkdb.kademlia.HashDistance; import net.i2p.util.Log; +import net.i2p.util.VersionComparator; /** * Coordinate the selection of peers to go into a tunnel for one particular * pool. + * + * Todo: there's nothing non-static in here */ public abstract class TunnelPeerSelector { /** @@ -36,7 +39,7 @@ public abstract class TunnelPeerSelector { * to build through, and the settings reject 0 hop tunnels, this will * return null. */ - public abstract List selectPeers(RouterContext ctx, TunnelPoolSettings settings); + public abstract List<Hash> selectPeers(RouterContext ctx, TunnelPoolSettings settings); protected int getLength(RouterContext ctx, TunnelPoolSettings settings) { int length = settings.getLength(); @@ -79,6 +82,11 @@ public abstract class TunnelPeerSelector { return length; } + /** + * For debugging, also possibly for restricted routes? + * Needs analysis and testing + * @return should always be false + */ protected boolean shouldSelectExplicit(TunnelPoolSettings settings) { if (settings.isExploratory()) return false; Properties opts = settings.getUnknownOptions(); @@ -92,7 +100,12 @@ public abstract class TunnelPeerSelector { return false; } - protected List selectExplicit(RouterContext ctx, TunnelPoolSettings settings, int length) { + /** + * For debugging, also possibly for restricted routes? + * Needs analysis and testing + * @return should always be false + */ + protected List<Hash> selectExplicit(RouterContext ctx, TunnelPoolSettings settings, int length) { String peers = null; Properties opts = settings.getUnknownOptions(); if (opts != null) @@ -102,7 +115,7 @@ public abstract class TunnelPeerSelector { peers = I2PAppContext.getGlobalContext().getProperty("explicitPeers"); Log log = ctx.logManager().getLog(ClientPeerSelector.class); - List rv = new ArrayList(); + List<Hash> rv = new ArrayList(); StringTokenizer tok = new StringTokenizer(peers, ","); while (tok.hasMoreTokens()) { String peerStr = tok.nextToken(); @@ -156,7 +169,7 @@ public abstract class TunnelPeerSelector { /** * Pick peers that we want to avoid */ - public Set getExclude(RouterContext ctx, boolean isInbound, boolean isExploratory) { + public Set<Hash> getExclude(RouterContext ctx, boolean isInbound, boolean isExploratory) { // we may want to update this to skip 'hidden' or 'unreachable' peers, but that // isn't safe, since they may publish one set of routerInfo to us and another to // other peers. the defaults for filterUnreachable has always been to return false, @@ -175,11 +188,12 @@ public abstract class TunnelPeerSelector { // // Defaults changed to true for inbound only in filterUnreachable below. - Set peers = new HashSet(1); + Set<Hash> 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)) { + // NOTE: filterUnreachable returns true for inbound, false for outbound // This is the only use for getPeersByCapability? And the whole set of datastructures in PeerManager? List<Hash> caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE); if (caps != null) @@ -189,6 +203,7 @@ public abstract class TunnelPeerSelector { peers.addAll(caps); } if (filterSlow(ctx, isInbound, isExploratory)) { + // NOTE: filterSlow always returns true Log log = ctx.logManager().getLog(TunnelPeerSelector.class); char excl[] = getExcludeCaps(ctx); if (excl != null) { @@ -301,6 +316,7 @@ public abstract class TunnelPeerSelector { return peers; } + /** warning, this is also called by ProfileOrganizer.isSelectable() */ public static boolean shouldExclude(RouterContext ctx, RouterInfo peer) { Log log = ctx.logManager().getLog(TunnelPeerSelector.class); return shouldExclude(ctx, log, peer, getExcludeCaps(ctx)); @@ -318,6 +334,10 @@ public abstract class TunnelPeerSelector { } private static final long DONT_EXCLUDE_PERIOD = 15*60*1000; + /** 0.7.8 and earlier had major message corruption bugs */ + private static final String MIN_VERSION = "0.7.9"; + private static final VersionComparator _versionComparator = new VersionComparator(); + private static boolean shouldExclude(RouterContext ctx, Log log, RouterInfo peer, char excl[]) { String cap = peer.getCapabilities(); if (cap == null) { @@ -340,6 +360,13 @@ public abstract class TunnelPeerSelector { // otherwise, it contains flags we aren't trying to focus on, // so don't exclude it based on published capacity + // minimum version check + String v = peer.getOption("router.version"); + if (v == null || _versionComparator.compare(v, MIN_VERSION) < 0) + return true; + + // uptime is always spoofed to 90m, so just remove all this + /****** String val = peer.getOption("stat_uptime"); if (val != null) { long uptimeMs = 0; @@ -390,6 +417,8 @@ public abstract class TunnelPeerSelector { // not publishing an uptime, so exclude it return true; } + ******/ + return false; } private static final String PROP_OUTBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE = "router.outboundExploratoryExcludeUnreachable"; @@ -403,6 +432,10 @@ public abstract class TunnelPeerSelector { private static final String DEFAULT_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE = "true"; private static final String DEFAULT_INBOUND_CLIENT_EXCLUDE_UNREACHABLE = "true"; + /** + * do we want to skip peers who haven't been up for long? + * @return true for inbound, false for outbound, unless configured otherwise + */ protected boolean filterUnreachable(RouterContext ctx, boolean isInbound, boolean isExploratory) { boolean def = false; String val = null; @@ -429,6 +462,10 @@ public abstract class TunnelPeerSelector { private static final String PROP_INBOUND_EXPLORATORY_EXCLUDE_SLOW = "router.inboundExploratoryExcludeSlow"; private static final String PROP_INBOUND_CLIENT_EXCLUDE_SLOW = "router.inboundClientExcludeSlow"; + /** + * do we want to skip peers that are slow? + * @return true unless configured otherwise + */ protected boolean filterSlow(RouterContext ctx, boolean isInbound, boolean isExploratory) { boolean def = true; String val = null; @@ -454,7 +491,10 @@ public abstract class TunnelPeerSelector { private static final String PROP_INBOUND_EXPLORATORY_EXCLUDE_UPTIME = "router.inboundExploratoryExcludeUptime"; private static final String PROP_INBOUND_CLIENT_EXCLUDE_UPTIME = "router.inboundClientExcludeUptime"; - /** do we want to skip peers who haven't been up for long? */ + /** + * do we want to skip peers who haven't been up for long? + * @return true unless configured otherwise + */ protected boolean filterUptime(RouterContext ctx, boolean isInbound, boolean isExploratory) { boolean def = true; String val = null;