package net.i2p.router; /* * 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.File; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import gnu.getopt.Getopt; import net.i2p.client.impl.I2PSessionImpl; import net.i2p.crypto.SigUtil; import net.i2p.data.Base64; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.PublicKey; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.router.RouterInfo; import net.i2p.router.CommSystemFacade.Status; import net.i2p.router.crypto.FamilyKeyCrypto; import net.i2p.router.message.GarlicMessageHandler; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.startup.CreateRouterInfoJob; import net.i2p.router.startup.StartupJob; import net.i2p.router.startup.WorkingDir; import net.i2p.router.tasks.*; import net.i2p.router.transport.FIFOBandwidthLimiter; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.router.util.EventLog; import net.i2p.stat.RateStat; import net.i2p.stat.StatManager; import net.i2p.util.ByteCache; import net.i2p.util.FortunaRandomSource; import net.i2p.util.I2PAppThread; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.NativeBigInteger; import net.i2p.util.OrderedProperties; import net.i2p.util.ReusableGZIPInputStream; import net.i2p.util.ReusableGZIPOutputStream; import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SimpleByteCache; import net.i2p.util.SystemVersion; import net.i2p.util.Translate; /** * Main driver for the router. * * For embedded use, instantiate, call setKillVMOnEnd(false), and then call runRouter(). * */ public class Router implements RouterClock.ClockShiftListener { private Log _log; private final RouterContext _context; private final Map<String, String> _config; /** full path */ private String _configFilename; private RouterInfo _routerInfo; private final Object _routerInfoLock = new Object(); /** not for external use */ public final Object routerInfoFileLock = new Object(); private final Object _configFileLock = new Object(); private long _started; private boolean _killVMOnEnd; private int _gracefulExitCode; private I2PThread.OOMEventListener _oomListener; private ShutdownHook _shutdownHook; private I2PThread _gracefulShutdownDetector; private RouterWatchdog _watchdog; private Thread _watchdogThread; private final EventLog _eventLog; private final Object _stateLock = new Object(); private State _state = State.UNINITIALIZED; private FamilyKeyCrypto _familyKeyCrypto; private boolean _familyKeyCryptoFail; public final Object _familyKeyLock = new Object(); public final static String PROP_CONFIG_FILE = "router.configLocation"; /** let clocks be off by 1 minute */ public final static long CLOCK_FUDGE_FACTOR = 1*60*1000; /** used to differentiate routerInfo files on different networks */ private static final int DEFAULT_NETWORK_ID = 2; private static final String PROP_NETWORK_ID = "router.networkID"; private final int _networkID; /** coalesce stats this often - should be a little less than one minute, so the graphs get updated */ public static final int COALESCE_TIME = 50*1000; /** this puts an 'H' in your routerInfo **/ public final static String PROP_HIDDEN = "router.hiddenMode"; /** this does not put an 'H' in your routerInfo **/ public final static String PROP_HIDDEN_HIDDEN = "router.isHidden"; public final static String PROP_DYNAMIC_KEYS = "router.dynamicKeys"; /** deprecated, use gracefulShutdownInProgress() */ private final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress"; public static final String PROP_IB_RANDOM_KEY = TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY + TunnelPoolSettings.PROP_RANDOM_KEY; public static final String PROP_OB_RANDOM_KEY = TunnelPoolSettings.PREFIX_OUTBOUND_EXPLORATORY + TunnelPoolSettings.PROP_RANDOM_KEY; private final static String DNS_CACHE_TIME = "" + (5*60); private static final String EVENTLOG = "eventlog.txt"; private static final String PROP_JBIGI = "jbigi.loadedResource"; public static final String UPDATE_FILE = "i2pupdate.zip"; private static final int SHUTDOWN_WAIT_SECS = 60; private static final String originalTimeZoneID; static { // // If embedding I2P you may wish to disable one or more of the following // via the associated System property. Since 0.9.19. // if (System.getProperty("I2P_DISABLE_DNS_CACHE_OVERRIDE") == null) { // grumble about sun's java caching DNS entries *forever* by default // so lets just keep 'em for a short time System.setProperty("sun.net.inetaddr.ttl", DNS_CACHE_TIME); System.setProperty("sun.net.inetaddr.negative.ttl", DNS_CACHE_TIME); System.setProperty("networkaddress.cache.ttl", DNS_CACHE_TIME); System.setProperty("networkaddress.cache.negative.ttl", DNS_CACHE_TIME); } if (System.getProperty("I2P_DISABLE_HTTP_AGENT_OVERRIDE") == null) { System.setProperty("http.agent", "I2P"); } if (System.getProperty("I2P_DISABLE_HTTP_KEEPALIVE_OVERRIDE") == null) { // (no need for keepalive) System.setProperty("http.keepAlive", "false"); } // Save it for LogManager originalTimeZoneID = TimeZone.getDefault().getID(); if (System.getProperty("I2P_DISABLE_TIMEZONE_OVERRIDE") == null) { System.setProperty("user.timezone", "GMT"); // just in case, lets make it explicit... TimeZone.setDefault(TimeZone.getTimeZone("GMT")); } // https://www.kb.cert.org/vuls/id/402580 // http://docs.codehaus.org/display/JETTY/SystemProperties // Fixed in Jetty 5.1.15 but we are running 5.1.12 // The default is true, unfortunately it was previously // set to false in wrapper.config thru 0.7.10 so we must set it back here. // Not in Jetty 7 //System.setProperty("org.mortbay.util.FileResource.checkAliases", "true"); } /** * Instantiation only. Starts no threads. Does not install updates. * RouterContext is created but not initialized. * You must call runRouter() after any constructor to start things up. * * Config file name is "router.config" unless router.configLocation set in system properties. * @throws IllegalStateException since 0.9.19 if another router with this config is running */ public Router() { this(null, null); } /** * Instantiation only. Starts no threads. Does not install updates. * RouterContext is created but not initialized. * You must call runRouter() after any constructor to start things up. * * Config file name is "router.config" unless router.configLocation set in envProps or system properties. * * @param envProps may be null * @throws IllegalStateException since 0.9.19 if another router with this config is running */ public Router(Properties envProps) { this(null, envProps); } /** * Instantiation only. Starts no threads. Does not install updates. * RouterContext is created but not initialized. * You must call runRouter() after any constructor to start things up. * * @param configFilename may be null * @throws IllegalStateException since 0.9.19 if another router with this config is running */ public Router(String configFilename) { this(configFilename, null); } /** * Instantiation only. Starts no threads. Does not install updates. * RouterContext is created but not initialized. * You must call runRouter() after any constructor to start things up. * * If configFilename is non-null, configuration is read in from there. * Else if envProps is non-null, configuration is read in from the * location given in the router.configLocation property. * Else it's read in from the System property router.configLocation. * Else from the file "router.config". * * The most important properties are i2p.dir.base (the install directory, may be read-only) * and i2p.dir.config (the user's configuration/data directory). * * i2p.dir.base defaults to user.dir (CWD) but should almost always be set. * * i2p.dir.config default depends on OS, user name (to detect if running as a service or not), * and auto-detection of whether there appears to be previous data files in the base dir. * See WorkingDir for details. * If the config dir does not exist, it will be created, and files migrated from the base dir, * in this constructor. * If files in an existing config dir indicate that another router is already running * with this directory, the constructor will delay for several seconds to be sure, * and then call System.exit(-1). * * @param configFilename may be null * @param envProps may be null * @throws IllegalStateException since 0.9.19 if another router with this config is running */ public Router(String configFilename, Properties envProps) { _killVMOnEnd = true; _gracefulExitCode = -1; _config = new ConcurrentHashMap<String, String>(); if (configFilename == null) { if (envProps != null) { _configFilename = envProps.getProperty(PROP_CONFIG_FILE); } if (_configFilename == null) _configFilename = System.getProperty(PROP_CONFIG_FILE, "router.config"); } else { _configFilename = configFilename; } // we need the user directory figured out by now, so figure it out here rather than // in the RouterContext() constructor. // // We have not read the config file yet. Therefore the base and config locations // are determined solely by properties (first envProps then System), for the purposes // of initializing the user's config directory if it did not exist. // If the base dir and/or config dir are set in the config file, // they wil be used after the initialization of the (possibly different) dirs // determined by WorkingDir. // So for now, it doesn't make much sense to set the base or config dirs in the config file - // use properties instead. If for some reason, distros need this, we can revisit it. // // Then add it to envProps (but not _config, we don't want it in the router.config file) // where it will then be available to all via _context.dir() // // This call also migrates all files to the new working directory, // including router.config // // Do we copy all the data files to the new directory? default false String migrate = System.getProperty("i2p.dir.migrate"); boolean migrateFiles = Boolean.parseBoolean(migrate); String userDir = WorkingDir.getWorkingDir(envProps, migrateFiles); // Use the router.config file specified in the router.configLocation property // (default "router.config"), // if it is an abolute path, otherwise look in the userDir returned by getWorkingDir // replace relative path with absolute File cf = new File(_configFilename); if (!cf.isAbsolute()) { cf = new File(userDir, _configFilename); _configFilename = cf.getAbsolutePath(); } readConfig(); if (envProps == null) envProps = new Properties(); envProps.putAll(_config); // This doesn't work, guess it has to be in the static block above? // if (Boolean.parseBoolean(envProps.getProperty("router.disableIPv6"))) // System.setProperty("java.net.preferIPv4Stack", "true"); if (envProps.getProperty("i2p.dir.config") == null) envProps.setProperty("i2p.dir.config", userDir); // Save this in the context for the logger and apps that need it envProps.setProperty("i2p.systemTimeZone", originalTimeZoneID); // Make darn sure we don't have a leftover I2PAppContext in the same JVM // e.g. on Android - see finalShutdown() also List<RouterContext> contexts = RouterContext.getContexts(); if (contexts.isEmpty()) { RouterContext.killGlobalContext(); } else if (SystemVersion.isAndroid()) { System.err.println("Warning: Killing " + contexts.size() + " other routers in this JVM"); contexts.clear(); RouterContext.killGlobalContext(); } else { System.err.println("Warning: " + contexts.size() + " other routers in this JVM"); } // The important thing that happens here is the directory paths are set and created // i2p.dir.router defaults to i2p.dir.config // i2p.dir.app defaults to i2p.dir.router // i2p.dir.log defaults to i2p.dir.router // i2p.dir.pid defaults to i2p.dir.router // i2p.dir.base defaults to user.dir == $CWD _context = new RouterContext(this, envProps); _eventLog = new EventLog(_context, new File(_context.getRouterDir(), EVENTLOG)); // This is here so that we can get the directory location from the context // for the ping file // Check for other router but do not start a thread yet so the update doesn't cause // a NCDFE for (int i = 0; i < 14; i++) { // Wrapper can start us up too quickly after a crash, the ping file // may still be less than LIVELINESS_DELAY (60s) old. // So wait at least 60s to be sure. if (isOnlyRouterRunning()) { if (i > 0) System.err.println("INFO: No, there wasn't another router already running. Proceeding with startup."); break; } if (i < 13) { if (i == 0) System.err.println("WARN: There may be another router already running. Waiting a while to be sure..."); // yes this is ugly to sleep in the constructor. try { Thread.sleep(5000); } catch (InterruptedException ie) {} } else { _eventLog.addEvent(EventLog.ABORTED, "Another router running"); System.err.println("ERROR: There appears to be another router already running!"); System.err.println(" Please make sure to shut down old instances before starting up"); System.err.println(" a new one. If you are positive that no other instance is running,"); System.err.println(" please delete the file " + getPingFile().getAbsolutePath()); //System.exit(-1); // throw exception instead, for embedded throw new IllegalStateException( "ERROR: There appears to be another router already running!" + " Please make sure to shut down old instances before starting up" + " a new one. If you are positive that no other instance is running," + " please delete the file " + getPingFile().getAbsolutePath()); } } if (_config.get("router.firstVersion") == null) { // These may be useful someday. First added in 0.8.2 _config.put("router.firstVersion", RouterVersion.VERSION); String now = Long.toString(System.currentTimeMillis()); _config.put("router.firstInstalled", now); _config.put("router.updateLastInstalled", now); // First added in 0.8.13 _config.put("router.previousVersion", RouterVersion.VERSION); saveConfig(); } int id = DEFAULT_NETWORK_ID; String sid = _config.get(PROP_NETWORK_ID); if (sid != null) { try { id = Integer.parseInt(sid); } catch (NumberFormatException nfe) {} } _networkID = id; changeState(State.INITIALIZED); // ********* Start no threads before here ********* // } /** * Initializes the RouterContext. * Starts some threads. Does not install updates. * All this was in the constructor. * * Could block for 10 seconds or forever if waiting for entropy * * @since 0.8.12 */ private void startupStuff() { // ********* Start no threads before here ********* // _log = _context.logManager().getLog(Router.class); // // NOW we can start the ping file thread. if (!SystemVersion.isAndroid()) beginMarkingLiveliness(); // Apps may use this as an easy way to determine if they are in the router JVM // But context.isRouterContext() is even easier... // Both of these as of 0.7.9 System.setProperty("router.version", RouterVersion.VERSION); // crypto init may block for 10 seconds waiting for entropy // we want to do this before context.initAll() // which will fire up several things that could block on the PRNG init warmupCrypto(); // NOW we start all the activity _context.initAll(); // Set wrapper.log permissions. // Just hope this is the right location, we don't know for sure, // but this is the same method used in LogsHelper and we have no complaints. // (we could look for the wrapper.config file and parse it I guess...) // If we don't have a wrapper, RouterLaunch does this for us. if (_context.hasWrapper()) { File f = new File(System.getProperty("java.io.tmpdir"), "wrapper.log"); if (!f.exists()) f = new File(_context.getBaseDir(), "wrapper.log"); if (f.exists()) SecureFileOutputStream.setPerms(f); } CryptoChecker.warnUnavailableCrypto(_context); _routerInfo = null; if (_log.shouldLog(Log.INFO)) _log.info("New router created with config file " + _configFilename); _oomListener = new OOMListener(_context); _shutdownHook = new ShutdownHook(_context); _gracefulShutdownDetector = new I2PAppThread(new GracefulShutdown(_context), "Graceful ShutdownHook", true); _gracefulShutdownDetector.setPriority(Thread.NORM_PRIORITY + 1); _gracefulShutdownDetector.start(); _watchdog = new RouterWatchdog(_context); _watchdogThread = new I2PAppThread(_watchdog, "RouterWatchdog", true); _watchdogThread.setPriority(Thread.NORM_PRIORITY + 1); _watchdogThread.start(); } /** * Not for external use. * @since 0.8.8 */ public static final void clearCaches() { ByteCache.clearAll(); SimpleByteCache.clearAll(); Destination.clearCache(); Translate.clearCache(); Hash.clearCache(); PublicKey.clearCache(); SigningPublicKey.clearCache(); SigUtil.clearCaches(); I2PSessionImpl.clearCache(); ReusableGZIPInputStream.clearCache(); ReusableGZIPOutputStream.clearCache(); } /** * Configure the router to kill the JVM when the router shuts down, as well * as whether to explicitly halt the JVM during the hard fail process. * * Defaults to true. Set to false for embedded before calling runRouter() */ public void setKillVMOnEnd(boolean shouldDie) { _killVMOnEnd = shouldDie; } /** @deprecated unused */ @Deprecated public boolean getKillVMOnEnd() { return _killVMOnEnd; } /** @return absolute path */ public String getConfigFilename() { return _configFilename; } /** @deprecated unused */ @Deprecated public void setConfigFilename(String filename) { _configFilename = filename; } public String getConfigSetting(String name) { return _config.get(name); } /** * Warning, race between here and saveConfig(), * saveConfig(String name, String value) or saveConfig(Map toAdd, Set toRemove) is recommended. * * @since 0.8.13 * @deprecated use saveConfig(String name, String value) or saveConfig(Map toAdd, Set toRemove) */ @Deprecated public void setConfigSetting(String name, String value) { _config.put(name, value); } /** * Warning, race between here and saveConfig(), * saveConfig(String name, String value) or saveConfig(Map toAdd, Set toRemove) is recommended. * * @since 0.8.13 * @deprecated use saveConfig(String name, String value) or saveConfig(Map toAdd, Set toRemove) */ @Deprecated public void removeConfigSetting(String name) { _config.remove(name); // remove the backing default also _context.removeProperty(name); } /** * @return unmodifiable Set, unsorted */ public Set<String> getConfigSettings() { return Collections.unmodifiableSet(_config.keySet()); } /** * @return unmodifiable Map, unsorted */ public Map<String, String> getConfigMap() { return Collections.unmodifiableMap(_config); } /** * Our current router info. * Warning, may be null if called very early. * * Warning - risk of deadlock - do not call while holding locks * */ public RouterInfo getRouterInfo() { synchronized (_routerInfoLock) { return _routerInfo; } } /** * Caller must ensure info is valid - no validation done here. * Not for external use. * * Warning - risk of deadlock - do not call while holding locks * */ public void setRouterInfo(RouterInfo info) { synchronized (_routerInfoLock) { _routerInfo = info; } if (_log.shouldLog(Log.INFO)) _log.info("setRouterInfo() : " + info, new Exception("I did it")); if (info != null) _context.jobQueue().addJob(new PersistRouterInfoJob(_context)); } /** * Used only by routerconsole.. to be deprecated? * @return System time, NOT context time */ public long getWhenStarted() { return _started; } /** * Wall clock uptime. * This uses System time, NOT context time, so context clock shifts will * not affect it. This is important if NTP fails and the * clock then shifts from a SSU peer source just after startup. */ public long getUptime() { if (_started <= 0) return 1000; // racing on startup return Math.max(1000, System.currentTimeMillis() - _started); } /** * The network ID. Default 2. * May be changed with the config property router.networkID (restart required). * Change only if running a test network to prevent cross-network contamination. * @since 0.9.25 */ public int getNetworkID() { return _networkID; } /** * Non-null, but take care when accessing context items before runRouter() is called * as the context will not be initialized. * * @return non-null */ public RouterContext getContext() { return _context; } /** * This must be called after instantiation. * Starts the threads. Does not install updates. * This is for embedded use. * Standard standalone installation uses main() instead, which * checks for updates and then calls this. * * This may take quite a while, especially if NTP fails * or the system lacks entropy * * @since public as of 0.9 for Android and other embedded uses * @throws IllegalStateException if called more than once */ public synchronized void runRouter() { synchronized(_stateLock) { if (_state != State.INITIALIZED) throw new IllegalStateException(); changeState(State.STARTING_1); } String last = _config.get("router.previousFullVersion"); if (last != null) { _eventLog.addEvent(EventLog.UPDATED, "from " + last + " to " + RouterVersion.FULL_VERSION); saveConfig("router.previousFullVersion", null); } _eventLog.addEvent(EventLog.STARTED, RouterVersion.FULL_VERSION); startupStuff(); changeState(State.STARTING_2); _started = System.currentTimeMillis(); try { Runtime.getRuntime().addShutdownHook(_shutdownHook); } catch (IllegalStateException ise) {} if (!SystemVersion.isAndroid()) I2PThread.addOOMEventListener(_oomListener); // message handlers _context.inNetMessagePool().registerHandlerJobBuilder(GarlicMessage.MESSAGE_TYPE, new GarlicMessageHandler(_context)); //if (ALLOW_DYNAMIC_KEYS) { // if ("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) // killKeys(); //} _context.messageValidator().startup(); _context.tunnelDispatcher().startup(); _context.inNetMessagePool().startup(); _context.jobQueue().runQueue(1); //_context.jobQueue().addJob(new CoalesceStatsJob(_context)); _context.simpleTimer2().addPeriodicEvent(new CoalesceStatsEvent(_context), COALESCE_TIME); _context.jobQueue().addJob(new UpdateRoutingKeyModifierJob(_context)); //_context.adminManager().startup(); _context.blocklist().startup(); synchronized(_configFileLock) { // persistent key for peer ordering since 0.9.17 // These will be replaced in CreateRouterInfoJob if we rekey if (!_config.containsKey(PROP_IB_RANDOM_KEY)) { byte rk[] = new byte[32]; _context.random().nextBytes(rk); _config.put(PROP_IB_RANDOM_KEY, Base64.encode(rk)); _context.random().nextBytes(rk); _config.put(PROP_OB_RANDOM_KEY, Base64.encode(rk)); saveConfig(); } } // let the timestamper get us sync'ed // this will block for quite a while on a disconnected machine long before = System.currentTimeMillis(); _context.clock().getTimestamper().waitForInitialization(); long waited = System.currentTimeMillis() - before; if (_log.shouldLog(Log.INFO)) _log.info("Waited " + waited + "ms to initialize"); changeState(State.STARTING_3); _context.jobQueue().addJob(new StartupJob(_context)); } /** * This updates the config with all settings found in the file. * It does not clear the config first, so settings not found in * the file will remain in the config. * * This is synchronized with saveConfig(). * Not for external use. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void readConfig() { synchronized(_configFileLock) { String f = getConfigFilename(); Properties config = getConfig(_context, f); // to avoid compiler errror Map foo = _config; foo.putAll(config); } } /** * this does not use ctx.getConfigDir(), must provide a full path in filename * Caller must synchronize * * @param ctx will be null at startup when called from constructor */ private static Properties getConfig(RouterContext ctx, String filename) { Log log = null; if (ctx != null) { log = ctx.logManager().getLog(Router.class); if (log.shouldLog(Log.DEBUG)) log.debug("Config file: " + filename, new Exception("location")); } Properties props = new Properties(); try { File f = new File(filename); if (f.canRead()) { DataHelper.loadProps(props, f); props.remove(PROP_SHUTDOWN_IN_PROGRESS); } else { if (log != null) log.warn("Configuration file " + filename + " does not exist"); // normal not to exist at first install //else // System.err.println("WARNING: Configuration file " + filename + " does not exist"); } } catch (IOException ioe) { if (log != null) log.error("Error loading the router configuration from " + filename, ioe); else System.err.println("Error loading the router configuration from " + filename + ": " + ioe); } return props; } ////////// begin state management /** * Startup / shutdown states * * @since 0.9.18 */ private enum State { UNINITIALIZED, /** constructor complete */ INITIALIZED, /** runRouter() called */ STARTING_1, /** startupStuff() complete, most of the time here is NTP */ STARTING_2, /** NTP done, Job queue started, StartupJob queued, runRouter() returned */ STARTING_3, /** RIs loaded. From STARTING_3 */ NETDB_READY, /** Non-zero-hop expl. tunnels built. From STARTING_3 */ EXPL_TUNNELS_READY, /** from NETDB_READY or EXPL_TUNNELS_READY */ RUNNING, /** * A "soft" restart, primarily of the comm system, after * a port change or large step-change in system time. * Does not stop the whole JVM, so it is safe even in the absence * of the wrapper. * This is not a graceful restart - all peer connections are dropped immediately. */ RESTARTING, /** cancellable shutdown has begun */ GRACEFUL_SHUTDOWN, /** In shutdown(). Non-cancellable shutdown has begun */ FINAL_SHUTDOWN_1, /** In shutdown2(). Killing everything */ FINAL_SHUTDOWN_2, /** In finalShutdown(). Final cleanup */ FINAL_SHUTDOWN_3, /** all done */ STOPPED } /** * @since 0.9.18 */ private void changeState(State state) { State oldState; synchronized(_stateLock) { oldState = _state; _state = state; } if (_log != null && state != State.STOPPED && _log.shouldLog(Log.WARN)) _log.warn("Router state change from " + oldState + " to " + state /* , new Exception() */ ); } /** * True during the initial start, but false during a soft restart. */ public boolean isAlive() { synchronized(_stateLock) { return _state == State.RUNNING || _state == State.GRACEFUL_SHUTDOWN || _state == State.STARTING_1 || _state == State.STARTING_2 || _state == State.STARTING_3 || _state == State.NETDB_READY || _state == State.EXPL_TUNNELS_READY; } } /** * Only for Restarter, after soft restart is complete. * Not for external use. * @since 0.8.12 */ public void setIsAlive() { changeState(State.RUNNING); } /** * Only for NetDB, after RIs are loaded. * Not for external use. * @since 0.9.18 */ public void setNetDbReady() { synchronized(_stateLock) { if (_state == State.STARTING_3) changeState(State.NETDB_READY); else if (_state == State.EXPL_TUNNELS_READY) changeState(State.RUNNING); } } /** * Only for Tunnel Building, after we have non-zero-hop expl. tunnels. * Not for external use. * @since 0.9.18 */ public void setExplTunnelsReady() { synchronized(_stateLock) { if (_state == State.STARTING_3) changeState(State.EXPL_TUNNELS_READY); else if (_state == State.NETDB_READY) changeState(State.RUNNING); } } /** * Is a graceful shutdown in progress? This may be cancelled. * Note that this also returns true if an uncancellable final shutdown is in progress. */ public boolean gracefulShutdownInProgress() { synchronized(_stateLock) { return _state == State.GRACEFUL_SHUTDOWN || _state == State.FINAL_SHUTDOWN_1 || _state == State.FINAL_SHUTDOWN_2 || _state == State.FINAL_SHUTDOWN_3 || _state == State.STOPPED; } } /** * Is a final shutdown in progress? This may not be cancelled. * @since 0.8.12 */ public boolean isFinalShutdownInProgress() { synchronized(_stateLock) { return _state == State.FINAL_SHUTDOWN_1 || _state == State.FINAL_SHUTDOWN_2 || _state == State.FINAL_SHUTDOWN_3 || _state == State.STOPPED; } } ////////// end state management /** * Rebuild and republish our routerInfo since something significant * has changed. * Not for external use. */ public void rebuildRouterInfo() { rebuildRouterInfo(false); } /** * Rebuild and republish our routerInfo since something significant * has changed. * Not for external use. * * Warning - risk of deadlock - do not call while holding locks * */ public void rebuildRouterInfo(boolean blockingRebuild) { if (_log.shouldLog(Log.INFO)) _log.info("Rebuilding new routerInfo"); synchronized (_routerInfoLock) { locked_rebuildRouterInfo(blockingRebuild); } } /** * Rebuild and republish our routerInfo since something significant * has changed. */ private void locked_rebuildRouterInfo(boolean blockingRebuild) { RouterInfo ri; if (_routerInfo != null) ri = new RouterInfo(_routerInfo); else ri = new RouterInfo(); try { ri.setPublished(_context.clock().now()); Properties stats = _context.statPublisher().publishStatistics(); ri.setOptions(stats); // deadlock thru createAddresses() thru SSU REA... move outside lock? ri.setAddresses(_context.commSystem().createAddresses()); SigningPrivateKey key = _context.keyManager().getSigningPrivateKey(); if (key == null) { _log.log(Log.CRIT, "Internal error - signing private key not known? Impossible?"); return; } ri.sign(key); setRouterInfo(ri); if (!ri.isValid()) throw new DataFormatException("Our RouterInfo has a bad signature"); Republish r = new Republish(_context); if (blockingRebuild) r.timeReached(); else _context.simpleTimer2().addEvent(r, 0); } catch (DataFormatException dfe) { _log.log(Log.CRIT, "Internal error - unable to sign our own address?!", dfe); } } /** * Family Key Crypto Signer / Verifier. * Not for external use. * If family key is set, first call Will take a while to generate keys. * Warning - risk of deadlock - do not call while holding locks * (other than routerInfoLock) * * @return null on initialization failure * @since 0.9.24 */ public FamilyKeyCrypto getFamilyKeyCrypto() { synchronized (_familyKeyLock) { if (_familyKeyCrypto == null) { if (!_familyKeyCryptoFail) { try { _familyKeyCrypto = new FamilyKeyCrypto(_context); } catch (GeneralSecurityException gse) { _log.error("Failed to initialize family key crypto", gse); _familyKeyCryptoFail = true; } } } } return _familyKeyCrypto; } // publicize our ballpark capacity public static final char CAPABILITY_BW12 = 'K'; public static final char CAPABILITY_BW32 = 'L'; public static final char CAPABILITY_BW64 = 'M'; public static final char CAPABILITY_BW128 = 'N'; public static final char CAPABILITY_BW256 = 'O'; /** @since 0.9.18 */ public static final char CAPABILITY_BW512 = 'P'; /** @since 0.9.18 */ public static final char CAPABILITY_BW_UNLIMITED = 'X'; /** for testing */ public static final String PROP_FORCE_BWCLASS = "router.forceBandwidthClass"; public static final char CAPABILITY_REACHABLE = 'R'; public static final char CAPABILITY_UNREACHABLE = 'U'; /** for testing */ public static final String PROP_FORCE_UNREACHABLE = "router.forceUnreachable"; /** @deprecated unused */ @Deprecated public static final char CAPABILITY_NEW_TUNNEL = 'T'; /** * The current bandwidth class. * For building our RI. Not for external use. * * @return a character to be added to the RI, one of "KLMNOPX" * @since 0.9.31 */ public char getBandwidthClass() { int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(), _context.bandwidthLimiter().getOutboundKBytesPerSecond()); bwLim = (int)(bwLim * getSharePercentage()); String force = _context.getProperty(PROP_FORCE_BWCLASS); if (force != null && force.length() > 0) { return force.charAt(0); } else if (bwLim < 12) { return CAPABILITY_BW12; } else if (bwLim <= 48) { return CAPABILITY_BW32; } else if (bwLim <= 64) { return CAPABILITY_BW64; } else if (bwLim <= 128) { return CAPABILITY_BW128; } else if (bwLim <= 256) { return CAPABILITY_BW256; } else if (bwLim <= 2000) { // TODO adjust threshold // 512 supported as of 0.9.18; return CAPABILITY_BW512; } else { // Unlimited supported as of 0.9.18; return CAPABILITY_BW_UNLIMITED; } } /** * For building our RI. Not for external use. * * @return a capabilities string to be added to the RI */ public String getCapabilities() { StringBuilder rv = new StringBuilder(4); char bw = getBandwidthClass(); rv.append(bw); // 512 and unlimited supported as of 0.9.18; // Add 256 as well for compatibility if (bw == CAPABILITY_BW512 || bw == CAPABILITY_BW_UNLIMITED) rv.append(CAPABILITY_BW256); // if prop set to true, don't tell people we are ff even if we are if (_context.netDb().floodfillEnabled() && !_context.getBooleanProperty("router.hideFloodfillParticipant")) rv.append(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL); if(_context.getBooleanProperty(PROP_HIDDEN)) rv.append(RouterInfo.CAPABILITY_HIDDEN); if (_context.getBooleanProperty(PROP_FORCE_UNREACHABLE)) { rv.append(CAPABILITY_UNREACHABLE); return rv.toString(); } switch (_context.commSystem().getStatus()) { case OK: case IPV4_OK_IPV6_UNKNOWN: case IPV4_OK_IPV6_FIREWALLED: case IPV4_FIREWALLED_IPV6_OK: case IPV4_DISABLED_IPV6_OK: case IPV4_UNKNOWN_IPV6_OK: case IPV4_SNAT_IPV6_OK: rv.append(CAPABILITY_REACHABLE); break; case DIFFERENT: case REJECT_UNSOLICITED: case IPV4_DISABLED_IPV6_FIREWALLED: rv.append(CAPABILITY_UNREACHABLE); break; case DISCONNECTED: case HOSED: case UNKNOWN: case IPV4_UNKNOWN_IPV6_FIREWALLED: case IPV4_DISABLED_IPV6_UNKNOWN: case IPV4_FIREWALLED_IPV6_UNKNOWN: case IPV4_SNAT_IPV6_UNKNOWN: default: // no explicit capability break; } return rv.toString(); } /* * This checks the config only. We don't check the current RI * due to deadlocks. * */ public boolean isHidden() { //RouterInfo ri; //synchronized (_routerInfoLock) { // ri = _routerInfo; //} //if ( (ri != null) && (ri.isHidden()) ) // return true; if (_context.getBooleanProperty(PROP_HIDDEN)) return true; String h = _context.getProperty(PROP_HIDDEN_HIDDEN); if (h != null) return Boolean.parseBoolean(h); return _context.commSystem().isInBadCountry(); } /** * @since 0.9.3 */ public EventLog eventLog() { return _eventLog; } /** * Ugly list of files that we need to kill if we are building a new identity * */ private static final String _rebuildFiles[] = new String[] { CreateRouterInfoJob.INFO_FILENAME, CreateRouterInfoJob.KEYS_FILENAME, CreateRouterInfoJob.KEYS2_FILENAME, "netDb/my.info", // no longer used "connectionTag.keys", // never used? KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_ENC, KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_ENC, KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_SIGNING, KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_SIGNING, "sessionKeys.dat" // no longer used }; /** * Not for external use. */ public void killKeys() { //new Exception("Clearing identity files").printStackTrace(); for (int i = 0; i < _rebuildFiles.length; i++) { File f = new File(_context.getRouterDir(),_rebuildFiles[i]); if (f.exists()) { boolean removed = f.delete(); if (removed) { System.out.println("INFO: Removing old identity file: " + _rebuildFiles[i]); } else { System.out.println("ERROR: Could not remove old identity file: " + _rebuildFiles[i]); } } } // now that we have random ports, keeping the same port would be bad synchronized(_configFileLock) { removeConfigSetting(UDPTransport.PROP_INTERNAL_PORT); removeConfigSetting(UDPTransport.PROP_EXTERNAL_PORT); removeConfigSetting(PROP_IB_RANDOM_KEY); removeConfigSetting(PROP_OB_RANDOM_KEY); saveConfig(); } } /** * Rebuild a new identity the hard way - delete all of our old identity * files, then reboot the router. * * Calls exit(), never returns. * * Not for external use. */ public synchronized void rebuildNewIdentity() { if (_shutdownHook != null) { try { Runtime.getRuntime().removeShutdownHook(_shutdownHook); } catch (IllegalStateException ise) {} } killKeys(); for (Runnable task : _context.getShutdownTasks()) { if (_log.shouldLog(Log.WARN)) _log.warn("Running shutdown task " + task.getClass()); try { task.run(); } catch (Throwable t) { _log.log(Log.CRIT, "Error running shutdown task", t); } } _context.removeShutdownTasks(); // hard and ugly if (_context.hasWrapper()) _log.log(Log.CRIT, "Restarting with new router identity"); else _log.log(Log.CRIT, "Shutting down because old router identity was invalid - restart I2P"); finalShutdown(EXIT_HARD_RESTART); } /** * Could block for 10 seconds or forever */ private void warmupCrypto() { _context.random().nextBoolean(); // Instantiate to fire up the YK refiller thread _context.elGamalEngine(); String loaded = NativeBigInteger.getLoadedResourceName(); if (loaded != null) saveConfig(PROP_JBIGI, loaded); } /** shut down after all tunnels are gone */ public static final int EXIT_GRACEFUL = 2; /** shut down immediately */ public static final int EXIT_HARD = 3; /** shut down immediately */ public static final int EXIT_OOM = 10; /** shut down immediately, and tell the wrapper to restart */ public static final int EXIT_HARD_RESTART = 4; /** shut down after all tunnels are gone, and tell the wrapper to restart */ public static final int EXIT_GRACEFUL_RESTART = 5; /** * Shutdown with no chance of cancellation. * Blocking, will call exit() and not return unless setKillVMOnExit(false) was previously called, * or a final shutdown is already in progress. * May take several seconds as it runs all the shutdown hooks. * * @param exitCode one of the EXIT_* values, non-negative * @throws IllegalArgumentException if exitCode negative */ public synchronized void shutdown(int exitCode) { if (exitCode < 0) throw new IllegalArgumentException(); synchronized(_stateLock) { if (_state == State.FINAL_SHUTDOWN_1 || _state == State.FINAL_SHUTDOWN_2 || _state == State.FINAL_SHUTDOWN_3 || _state == State.STOPPED) return; changeState(State.FINAL_SHUTDOWN_1); } _context.throttle().setShutdownStatus(); if (_shutdownHook != null) { try { Runtime.getRuntime().removeShutdownHook(_shutdownHook); } catch (IllegalStateException ise) {} } shutdown2(exitCode); } /** * Cancel the JVM runtime hook before calling this. * Called by the ShutdownHook. * NOT to be called by others, use shutdown(). * * @param exitCode one of the EXIT_* values, non-negative * @throws IllegalArgumentException if exitCode negative */ public synchronized void shutdown2(int exitCode) { if (exitCode < 0) throw new IllegalArgumentException(); changeState(State.FINAL_SHUTDOWN_2); // help us shut down esp. after OOM int priority = (exitCode == EXIT_OOM) ? Thread.MAX_PRIORITY - 1 : Thread.NORM_PRIORITY + 2; Thread.currentThread().setPriority(priority); _log.log(Log.CRIT, "Starting final shutdown(" + exitCode + ')'); // So we can get all the way to the end // No, you can't do Thread.currentThread.setDaemon(false) if (_killVMOnEnd) { try { (new Spinner()).start(); } catch (Throwable t) {} } ((RouterClock) _context.clock()).removeShiftListener(this); _context.random().saveSeed(); I2PThread.removeOOMEventListener(_oomListener); // Run the shutdown hooks first in case they want to send some goodbye messages // Maybe we need a delay after this too? LinkedList<Thread> tasks = new LinkedList<Thread>(); for (Runnable task : _context.getShutdownTasks()) { //System.err.println("Running shutdown task " + task.getClass()); if (_log.shouldLog(Log.WARN)) _log.warn("Running shutdown task " + task.getClass()); try { //task.run(); Thread t = new I2PAppThread(task, "Shutdown task " + task.getClass().getName()); t.setDaemon(true); t.start(); tasks.add(t); } catch (Throwable t) { _log.log(Log.CRIT, "Error running shutdown task", t); } } long waitSecs = SHUTDOWN_WAIT_SECS; if (SystemVersion.isARM()) waitSecs *= 2; final long maxWait = System.currentTimeMillis() + (waitSecs *1000); Thread th; while ((th = tasks.poll()) != null) { long toWait = maxWait - System.currentTimeMillis(); if (toWait <= 0) { _log.logAlways(Log.WARN, "Shutdown tasks took more than " + waitSecs + " seconds to run"); tasks.clear(); break; } try { th.join(toWait); } catch (InterruptedException ie) {} if (th.isAlive()) { _log.logAlways(Log.WARN, "Shutdown task took more than " + waitSecs + " seconds to run: " + th.getName()); tasks.clear(); break; } else if (_log.shouldInfo()) { _log.info("Shutdown task complete: " + th.getName()); } } // Set the last version to the current version, since 0.8.13 if (!RouterVersion.VERSION.equals(_config.get("router.previousVersion"))) { saveConfig("router.previousVersion", RouterVersion.VERSION); } _context.removeShutdownTasks(); try { _context.clientManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the client manager", t); } try { _context.namingService().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the naming service", t); } try { _context.jobQueue().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the job queue", t); } try { _context.tunnelManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the tunnel manager", t); } try { _context.tunnelDispatcher().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the tunnel dispatcher", t); } try { _context.netDb().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the networkDb", t); } try { _context.commSystem().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the comm system", t); } try { _context.bandwidthLimiter().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the comm system", t); } try { _context.peerManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the peer manager", t); } try { _context.messageRegistry().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the message registry", t); } try { _context.messageValidator().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the message validator", t); } try { _context.inNetMessagePool().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the inbound net pool", t); } try { _context.clientMessagePool().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the client msg pool", t); } try { _context.sessionKeyManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the session key manager", t); } try { _context.messageHistory().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the message history logger", t); } // do stat manager last to reduce chance of NPEs in other threads try { _context.statManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the stats manager", t); } _context.deleteTempDir(); List<RouterContext> contexts = RouterContext.getContexts(); contexts.remove(_context); // shut down I2PAppContext tasks here try { _context.elGamalEngine().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting elGamal", t); } if (contexts.isEmpty()) { // any thing else to shut down? } else { _log.logAlways(Log.WARN, "Warning - " + contexts.size() + " routers remaining in this JVM, not releasing all resources"); } try { ((FortunaRandomSource)_context.random()).shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting random()", t); } // logManager shut down in finalShutdown() _watchdog.shutdown(); _watchdogThread.interrupt(); _eventLog.addEvent(EventLog.STOPPED, Integer.toString(exitCode)); finalShutdown(exitCode); } /** * disable dynamic key functionality for the moment, as it may be harmful and doesn't * add meaningful anonymity */ private static final boolean ALLOW_DYNAMIC_KEYS = false; /** * Cancel the JVM runtime hook before calling this. * * @param exitCode one of the EXIT_* values, non-negative */ private synchronized void finalShutdown(int exitCode) { changeState(State.FINAL_SHUTDOWN_3); clearCaches(); _log.log(Log.CRIT, "Shutdown(" + exitCode + ") complete" /* , new Exception("Shutdown") */ ); try { _context.logManager().shutdown(); } catch (Throwable t) { } if (ALLOW_DYNAMIC_KEYS) { if (_context.getBooleanProperty(PROP_DYNAMIC_KEYS)) killKeys(); } File f = getPingFile(); f.delete(); if (RouterContext.getContexts().isEmpty()) RouterContext.killGlobalContext(); // Since 0.8.8, for Android and the wrapper for (Runnable task : _context.getFinalShutdownTasks()) { //System.err.println("Running final shutdown task " + task.getClass()); try { task.run(); } catch (Throwable t) { System.err.println("Running final shutdown task " + t); } } _context.getFinalShutdownTasks().clear(); if (_killVMOnEnd) { try { Thread.sleep(1000); } catch (InterruptedException ie) {} //Runtime.getRuntime().halt(exitCode); // allow the Runtime shutdown hooks to execute Runtime.getRuntime().exit(exitCode); } else if (SystemVersion.isAndroid()) { Runtime.getRuntime().gc(); } changeState(State.STOPPED); } /** * Non-blocking shutdown. * * Call this if we want the router to kill itself as soon as we aren't * participating in any more tunnels (etc). This will not block and doesn't * guarantee any particular time frame for shutting down. To shut the * router down immediately, use {@link #shutdown}. If you want to cancel * the graceful shutdown (prior to actual shutdown ;), call * {@link #cancelGracefulShutdown}. * * Exit code will be EXIT_GRACEFUL. * * Shutdown delay will be from zero to 11 minutes. */ public void shutdownGracefully() { shutdownGracefully(EXIT_GRACEFUL); } /** * Non-blocking shutdown. * * Call this with EXIT_HARD or EXIT_HARD_RESTART for a non-blocking, * hard, non-graceful shutdown with a brief delay to allow a UI response * * Returns silently if a final shutdown is already in progress. * * @param exitCode one of the EXIT_* values, non-negative * @throws IllegalArgumentException if exitCode negative */ public void shutdownGracefully(int exitCode) { if (exitCode < 0) throw new IllegalArgumentException(); synchronized(_stateLock) { if (isFinalShutdownInProgress()) return; // too late changeState(State.GRACEFUL_SHUTDOWN); _gracefulExitCode = exitCode; } //_config.put(PROP_SHUTDOWN_IN_PROGRESS, "true"); _context.throttle().setShutdownStatus(); synchronized (_gracefulShutdownDetector) { _gracefulShutdownDetector.notifyAll(); } } /** * Cancel any prior request to shut the router down gracefully. * * Returns silently if a final shutdown is already in progress. */ public void cancelGracefulShutdown() { synchronized(_stateLock) { if (isFinalShutdownInProgress()) return; // too late changeState(State.RUNNING); _gracefulExitCode = -1; } //_config.remove(PROP_SHUTDOWN_IN_PROGRESS); _context.throttle().cancelShutdownStatus(); synchronized (_gracefulShutdownDetector) { _gracefulShutdownDetector.notifyAll(); } } /** * What exit code do we plan on using when we shut down (or -1, if there isn't a graceful shutdown planned) * * @return one of the EXIT_* values or -1 */ public int scheduledGracefulExitCode() { synchronized(_stateLock) { return _gracefulExitCode; } } /** * How long until the graceful shutdown will kill us? * @return -1 if no shutdown in progress. */ public long getShutdownTimeRemaining() { synchronized(_stateLock) { if (_gracefulExitCode <= 0) return -1; // maybe Long.MAX_VALUE would be better? if (_gracefulExitCode == EXIT_HARD || _gracefulExitCode == EXIT_HARD_RESTART) return 0; } long exp = _context.tunnelManager().getLastParticipatingExpiration(); if (exp < 0) return 0; else return Math.max(0, exp + 2*CLOCK_FUDGE_FACTOR - _context.clock().now()); } /** * Save the current config options (returning true if save was * successful, false otherwise) * * Synchronized with file read in getConfig() */ public boolean saveConfig() { try { Properties ordered = new OrderedProperties(); synchronized(_configFileLock) { ordered.putAll(_config); DataHelper.storeProps(ordered, new File(_configFilename)); } } catch (IOException ioe) { // warning, _log will be null when called from constructor if (_log != null) _log.error("Error saving the config to " + _configFilename, ioe); else System.err.println("Error saving the config to " + _configFilename + ": " + ioe); return false; } return true; } /** * Updates the current config with the given key/value and then saves it. * Prevents a race in the interval between setConfigSetting() / removeConfigSetting() and saveConfig(), * Synchronized with getConfig() / saveConfig() * * @param name setting to add/change/remove before saving * @param value if non-null, updated value; if null, setting will be removed * @return success * @since 0.8.13 */ public boolean saveConfig(String name, String value) { synchronized(_configFileLock) { if (value != null) _config.put(name, value); else removeConfigSetting(name); return saveConfig(); } } /** * Updates the current config and then saves it. * Prevents a race in the interval between setConfigSetting() / removeConfigSetting() and saveConfig(), * Synchronized with getConfig() / saveConfig() * * @param toAdd settings to add/change before saving, may be null or empty * @param toRemove settings to remove before saving, may be null or empty * @return success * @since 0.8.13 */ @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean saveConfig(Map toAdd, Collection<String> toRemove) { synchronized(_configFileLock) { if (toAdd != null) _config.putAll(toAdd); if (toRemove != null) { for (String s : toRemove) { removeConfigSetting(s); } } return saveConfig(); } } /** * The clock shift listener. * Restart the router if we should. * * @since 0.8.8 */ public void clockShift(long delta) { if (delta > -60*1000 && delta < 60*1000) return; synchronized(_stateLock) { if (gracefulShutdownInProgress() || !isAlive()) return; } _eventLog.addEvent(EventLog.CLOCK_SHIFT, Long.toString(delta)); // update the routing key modifier _context.routerKeyGenerator().generateDateBasedModData(); // Commented because this check makes no sense (#1014) // .. and 'active' is relative to our broken time. //if (_context.commSystem().countActivePeers() <= 0) // return; if (delta > 0) _log.error("Restarting after large clock shift forward by " + DataHelper.formatDuration(delta)); else _log.error("Restarting after large clock shift backward by " + DataHelper.formatDuration(0 - delta)); restart(); } /** * A "soft" restart, primarily of the comm system, after * a port change or large step-change in system time. * Does not stop the whole JVM, so it is safe even in the absence * of the wrapper. * This is not a graceful restart - all peer connections are dropped immediately. * * As of 0.8.8, this returns immediately and does the actual restart in a separate thread. * Poll isAlive() if you need to know when the restart is complete. * * Not recommended for external use. */ public synchronized void restart() { synchronized(_stateLock) { if (gracefulShutdownInProgress() || !isAlive()) return; changeState(State.RESTARTING); } ((RouterClock) _context.clock()).removeShiftListener(this); // Let's not stop accepting tunnels, etc //_started = _context.clock().now(); Thread t = new I2PThread(new Restarter(_context), "Router Restart"); t.setPriority(Thread.NORM_PRIORITY + 1); t.start(); } /** * Usage: Router [rebuild] * No other options allowed, for now * Instantiates Router(), and either installs updates and exits, * or calls runRouter(). * * Not recommended for embedded use. * Applications bundling I2P should instantiate a Router and call runRouter(). * * @param args null ok * @throws IllegalArgumentException */ public static void main(String args[]) { boolean rebuild = false; if (args != null) { boolean error = false; Getopt g = new Getopt("router", args, ""); int c; while ((c = g.getopt()) != -1) { switch (c) { default: error = true; } } int remaining = args.length - g.getOptind(); if (remaining > 1) { error = true; } else if (remaining == 1) { rebuild = args[g.getOptind()].equals("rebuild");; if (!rebuild) error = true; } if (error) throw new IllegalArgumentException(); } System.out.println("Starting I2P " + RouterVersion.FULL_VERSION); //verifyWrapperConfig(); Router r; try { r = new Router(); } catch (IllegalStateException ise) { System.exit(-1); return; } if (rebuild) { r.rebuildNewIdentity(); } else { // This is here so that we can get the directory location from the context // for the zip file and the base location to unzip to. // If it does an update, it never returns. // I guess it's better to have the other-router check above this, we don't want to // overwrite an existing running router's jar files. Other than ours. InstallUpdate.installUpdates(r); // ********* Start no threads before here ********* // r.runRouter(); } } /******* private static void verifyWrapperConfig() { File cfgUpdated = new File("wrapper.config.updated"); if (cfgUpdated.exists()) { cfgUpdated.delete(); System.out.println("INFO: Wrapper config updated, but the service wrapper requires you to manually restart"); System.out.println("INFO: Shutting down the router - please rerun it!"); System.exit(EXIT_HARD); } } *******/ /* private static String getPingFile(Properties envProps) { if (envProps != null) return envProps.getProperty("router.pingFile", "router.ping"); else return "router.ping"; } */ private File getPingFile() { String s = _context.getProperty("router.pingFile", "router.ping"); File f = new File(s); if (!f.isAbsolute()) f = new File(_context.getPIDDir(), s); return f; } private static final long LIVELINESS_DELAY = 60*1000; /** * Check the file "router.ping", but if * that file already exists and was recently written to, return false as there is * another instance running. * * @return true if the router is the only one running * @since 0.8.2 */ private boolean isOnlyRouterRunning() { File f = getPingFile(); if (f.exists()) { long lastWritten = f.lastModified(); if (System.currentTimeMillis()-lastWritten > LIVELINESS_DELAY) { System.err.println("WARN: Old router was not shut down gracefully, deleting " + f); f.delete(); } else { return false; } } return true; } /** * Start a thread that will periodically update the file "router.ping". * isOnlyRouterRunning() MUST have been called previously. */ private void beginMarkingLiveliness() { File f = getPingFile(); _context.simpleTimer2().addPeriodicEvent(new MarkLiveliness(this, f), 0, LIVELINESS_DELAY - (5*1000)); } public static final String PROP_BANDWIDTH_SHARE_PERCENTAGE = "router.sharePercentage"; public static final int DEFAULT_SHARE_PERCENTAGE = 80; /** * What fraction of the bandwidth specified in our bandwidth limits should * we allow to be consumed by participating tunnels? * @return a number less than one, not a percentage! * */ public double getSharePercentage() { String pct = _context.getProperty(PROP_BANDWIDTH_SHARE_PERCENTAGE); if (pct != null) { try { double d = Double.parseDouble(pct); if (d > 1) return d/100d; // *cough* sometimes its 80 instead of .8 (!stab jrandom) else return d; } catch (NumberFormatException nfe) { if (_log.shouldLog(Log.INFO)) _log.info("Unable to get the share percentage"); } } return DEFAULT_SHARE_PERCENTAGE / 100.0d; } /** * Max of inbound and outbound rate in bytes per second */ public int get1sRate() { return get1sRate(false); } /** * When outboundOnly is false, outbound rate in bytes per second. * When true, max of inbound and outbound rate in bytes per second. */ public int get1sRate(boolean outboundOnly) { FIFOBandwidthLimiter bw = _context.bandwidthLimiter(); int out = (int)bw.getSendBps(); if (outboundOnly) return out; return (int)Math.max(out, bw.getReceiveBps()); } /** * Inbound rate in bytes per second */ public int get1sRateIn() { FIFOBandwidthLimiter bw = _context.bandwidthLimiter(); return (int) bw.getReceiveBps(); } /** * Max of inbound and outbound rate in bytes per second */ public int get15sRate() { return get15sRate(false); } /** * When outboundOnly is false, outbound rate in bytes per second. * When true, max of inbound and outbound rate in bytes per second. */ public int get15sRate(boolean outboundOnly) { FIFOBandwidthLimiter bw = _context.bandwidthLimiter(); int out = (int)bw.getSendBps15s(); if (outboundOnly) return out; return (int)Math.max(out, bw.getReceiveBps15s()); } /** * Inbound rate in bytes per second */ public int get15sRateIn() { FIFOBandwidthLimiter bw = _context.bandwidthLimiter(); return (int) bw.getReceiveBps15s(); } /** * Max of inbound and outbound rate in bytes per second */ public int get1mRate() { return get1mRate(false); } /** * When outboundOnly is false, outbound rate in bytes per second. * When true, max of inbound and outbound rate in bytes per second. */ public int get1mRate(boolean outboundOnly) { int send = 0; StatManager mgr = _context.statManager(); RateStat rs = mgr.getRate("bw.sendRate"); if (rs != null) send = (int)rs.getRate(1*60*1000).getAverageValue(); if (outboundOnly) return send; int recv = 0; rs = mgr.getRate("bw.recvRate"); if (rs != null) recv = (int)rs.getRate(1*60*1000).getAverageValue(); return Math.max(send, recv); } /** * Inbound rate in bytes per second */ public int get1mRateIn() { StatManager mgr = _context.statManager(); RateStat rs = mgr.getRate("bw.recvRate"); int recv = 0; if (rs != null) recv = (int)rs.getRate(1*60*1000).getAverageValue(); return recv; } /** * Max of inbound and outbound rate in bytes per second */ public int get5mRate() { return get5mRate(false); } /** * When outboundOnly is false, outbound rate in bytes per second. * When true, max of inbound and outbound rate in bytes per second. */ public int get5mRate(boolean outboundOnly) { int send = 0; RateStat rs = _context.statManager().getRate("bw.sendRate"); if (rs != null) send = (int)rs.getRate(5*60*1000).getAverageValue(); if (outboundOnly) return send; int recv = 0; rs = _context.statManager().getRate("bw.recvRate"); if (rs != null) recv = (int)rs.getRate(5*60*1000).getAverageValue(); return Math.max(send, recv); } }