package net.i2p.router;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.data.Hash;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.router.RouterKeyGenerator;
import net.i2p.internal.InternalClientManager;
import net.i2p.router.client.ClientManagerFacadeImpl;
import net.i2p.router.crypto.TransientSessionKeyManager;
import net.i2p.router.dummy.*;
import net.i2p.router.message.GarlicMessageParser;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.peermanager.PeerManagerFacadeImpl;
import net.i2p.router.peermanager.ProfileManagerImpl;
import net.i2p.router.peermanager.ProfileOrganizer;
import net.i2p.router.startup.RouterAppManager;
import net.i2p.router.transport.CommSystemFacadeImpl;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.router.transport.OutboundMessageRegistry;
import net.i2p.router.tunnel.TunnelDispatcher;
import net.i2p.router.tunnel.pool.TunnelPoolManager;
import net.i2p.util.KeyRing;
import net.i2p.util.I2PProperties.I2PPropertyCallback;
import net.i2p.util.SystemVersion;
/**
* Build off the core I2P context to provide a root for a router instance to
* coordinate its resources. Router instances themselves should be sure to have
* their own RouterContext, and rooting off of it will allow multiple routers to
* operate in the same JVM without conflict (e.g. sessionTags wont get
* intermingled, nor will their netDbs, jobQueues, or bandwidth limiters).
*
*/
public class RouterContext extends I2PAppContext {
private final Router _router;
private ClientManagerFacade _clientManagerFacade;
private InternalClientManager _internalClientManager;
private ClientMessagePool _clientMessagePool;
private JobQueue _jobQueue;
private InNetMessagePool _inNetMessagePool;
private OutNetMessagePool _outNetMessagePool;
private MessageHistory _messageHistory;
private OutboundMessageRegistry _messageRegistry;
private NetworkDatabaseFacade _netDb;
private KeyManager _keyManager;
private CommSystemFacade _commSystem;
private ProfileOrganizer _profileOrganizer;
private PeerManagerFacade _peerManagerFacade;
private ProfileManager _profileManager;
private FIFOBandwidthLimiter _bandwidthLimiter;
private TunnelManagerFacade _tunnelManager;
private TunnelDispatcher _tunnelDispatcher;
private StatisticsManager _statPublisher;
private Banlist _banlist;
private Blocklist _blocklist;
private MessageValidator _messageValidator;
//private MessageStateMonitor _messageStateMonitor;
private RouterThrottle _throttle;
private RouterAppManager _appManager;
private RouterKeyGenerator _routingKeyGenerator;
private GarlicMessageParser _garlicMessageParser;
private final Set<Runnable> _finalShutdownTasks;
// split up big lock on this to avoid deadlocks
private volatile boolean _initialized;
private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object();
private static final List<RouterContext> _contexts = new CopyOnWriteArrayList<RouterContext>();
/**
* Caller MUST call initAll() after instantiation.
*/
public RouterContext(Router router) { this(router, null); }
/**
* Caller MUST call initAll() after instantiation.
*/
public RouterContext(Router router, Properties envProps) {
super(filterProps(envProps));
_router = router;
// Disabled here so that the router can get a context and get the
// directory locations from it, to do an update, without having
// to init everything. Caller MUST call initAll() afterwards.
// Sorry, this breaks some main() unit tests out there.
//initAll();
if (!_contexts.isEmpty())
System.err.println("Warning - More than one router in this JVM");
_finalShutdownTasks = new CopyOnWriteArraySet<Runnable>();
_contexts.add(this);
}
/**
* Set properties where the defaults must be different from those
* in I2PAppContext.
*
* Unless we are explicitly disabling the timestamper, we want to use it.
* We need this now as the new timestamper default is disabled (so we don't
* have each I2PAppContext creating their own SNTP queries all the time)
*
* Set more PRNG buffers, as the default is now small for the I2PAppContext.
*
*/
private static final Properties filterProps(Properties envProps) {
if (envProps == null)
envProps = new Properties();
if (envProps.getProperty("time.disabled") == null)
envProps.setProperty("time.disabled", "false");
if (envProps.getProperty("prng.buffers") == null) {
// How many of these 256 KB buffers do we need?
// One clue: prng.bufferFillTime is ~10ms on my system,
// and prng.bufferFillTime event count is ~30 per minute,
// or about 2 seconds per buffer - so about 200x faster
// to fill than to drain - so we don't need too many
long maxMemory = SystemVersion.getMaxMemory();
long maxBuffs = (SystemVersion.isAndroid() || SystemVersion.isARM()) ? 4 : 8;
long buffs = Math.min(maxBuffs, Math.max(2, maxMemory / (21 * 1024 * 1024)));
envProps.setProperty("prng.buffers", "" + buffs);
}
return envProps;
}
/**
* Modify the configuration attributes of this context, changing
* one of the properties provided during the context construction.
*
* @param propName The name of the property.
* @param value The new value for the property.
* @since 0.8.4
* @deprecated Use Router.saveConfig()
*/
@Deprecated
public void setProperty(String propName, String value) {
_overrideProps.setProperty(propName, value);
}
/**
* Remove a property provided during the context construction.
* Only for use by the router. Others use Router.saveConfig()
*
* @param propName The name of the property.
* @since 0.9
*/
void removeProperty(String propName) {
_overrideProps.remove(propName);
}
public void addPropertyCallback(I2PPropertyCallback callback) {
_overrideProps.addCallBack(callback);
}
/**
* The following properties may be used to replace various parts
* of the context with dummy implementations for testing, by setting
* the property to "true":
*<pre>
* i2p.dummyClientFacade
* i2p.dummyNetDb
* i2p.dummyPeerManager
* i2p.dummyTunnelManager
* i2p.vmCommSystem (transport)
*</pre>
*/
public synchronized void initAll() {
if (_initialized)
throw new IllegalStateException();
if (!getBooleanProperty("i2p.dummyClientFacade")) {
ClientManagerFacadeImpl cmfi = new ClientManagerFacadeImpl(this);
_clientManagerFacade = cmfi;
_internalClientManager = cmfi;
} else {
_clientManagerFacade = new DummyClientManagerFacade(this);
// internal client manager is null
}
_garlicMessageParser = new GarlicMessageParser(this);
_clientMessagePool = new ClientMessagePool(this);
_jobQueue = new JobQueue(this);
_jobQueue.startup();
_inNetMessagePool = new InNetMessagePool(this);
_outNetMessagePool = new OutNetMessagePool(this);
_messageHistory = new MessageHistory(this);
_messageRegistry = new OutboundMessageRegistry(this);
//_messageStateMonitor = new MessageStateMonitor(this);
_routingKeyGenerator = new RouterKeyGenerator(this);
if (!getBooleanProperty("i2p.dummyNetDb"))
_netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this);
else
_netDb = new DummyNetworkDatabaseFacade(this);
_keyManager = new KeyManager(this);
if (!getBooleanProperty("i2p.vmCommSystem"))
_commSystem = new CommSystemFacadeImpl(this);
else
_commSystem = new VMCommSystem(this);
_profileOrganizer = new ProfileOrganizer(this);
if (!getBooleanProperty("i2p.dummyPeerManager"))
_peerManagerFacade = new PeerManagerFacadeImpl(this);
else
_peerManagerFacade = new DummyPeerManagerFacade();
_profileManager = new ProfileManagerImpl(this);
_bandwidthLimiter = new FIFOBandwidthLimiter(this);
if (!getBooleanProperty("i2p.dummyTunnelManager"))
_tunnelManager = new TunnelPoolManager(this);
else
_tunnelManager = new DummyTunnelManagerFacade();
_tunnelDispatcher = new TunnelDispatcher(this);
_statPublisher = new StatisticsManager(this);
_banlist = new Banlist(this);
_blocklist = new Blocklist(this);
_messageValidator = new MessageValidator(this);
_throttle = new RouterThrottleImpl(this);
//_throttle = new RouterDoSThrottle(this);
_appManager = new RouterAppManager(this);
_initialized = true;
}
/**
* Retrieve the list of router contexts currently instantiated in this JVM.
* This will always contain only one item (except when a simulation per the
* MultiRouter is going on).
*
* @return an unmodifiable list (as of 0.8.8). May be empty.
*/
public static List<RouterContext> listContexts() {
return Collections.unmodifiableList(_contexts);
}
/**
* Same as listContexts() but package private and modifiable.
* The list should only be modified when a new
* context is created or a router is shut down.
*
* @since 0.8.8
*/
static List<RouterContext> getContexts() {
return _contexts;
}
/**
* Kill the global I2PAppContext, so it isn't still around
* when we restart in the same JVM (Android).
* Only do this if there are no other routers in the JVM.
*
* @since 0.8.8
*/
static void killGlobalContext() {
synchronized (I2PAppContext.class) {
_globalAppContext = null;
}
}
/** what router is this context working for? */
public Router router() { return _router; }
/**
* Convenience method for getting the router hash.
* Equivalent to context.router().getRouterInfo().getIdentity().getHash()
*
* Warning - risk of deadlock - do not call while holding locks
*
* @return may be null if called very early
*/
public Hash routerHash() {
if (_router == null)
return null;
RouterInfo ri = _router.getRouterInfo();
if (ri == null)
return null;
return ri.getIdentity().getHash();
}
/**
* How are we coordinating clients for the router?
*/
public ClientManagerFacade clientManager() { return _clientManagerFacade; }
/**
* Where do we toss messages for the clients (and where do we get client messages
* to forward on from)?
*/
public ClientMessagePool clientMessagePool() { return _clientMessagePool; }
/**
* Where do we get network messages from (aka where does the comm system dump what
* it reads)?
*/
public InNetMessagePool inNetMessagePool() { return _inNetMessagePool; }
/**
* Where do we put messages that the router wants to forwards onto the network?
*/
public OutNetMessagePool outNetMessagePool() { return _outNetMessagePool; }
/**
* Tracker component for monitoring what messages are wrapped in what containers
* and how they proceed through the network. This is fully for debugging, as when
* a large portion of the network tracks their messages through this messageHistory
* and submits their logs, we can correlate them and watch as messages flow from
* hop to hop.
*/
public MessageHistory messageHistory() { return _messageHistory; }
/**
* The registry is used by outbound messages to wait for replies.
*/
public OutboundMessageRegistry messageRegistry() { return _messageRegistry; }
/**
* The monitor keeps track of inbound and outbound messages currently held in
* memory / queued for processing. We'll use this to throttle the router so
* we don't overflow.
*
*/
//public MessageStateMonitor messageStateMonitor() { return _messageStateMonitor; }
/**
* Our db cache
*/
public NetworkDatabaseFacade netDb() { return _netDb; }
/**
* The actual driver of the router, where all jobs are enqueued and processed.
*/
public JobQueue jobQueue() { return _jobQueue; }
/**
* Coordinates the router's ElGamal and DSA keys, as well as any keys given
* to it by clients as part of a LeaseSet.
*/
public KeyManager keyManager() { return _keyManager; }
/**
* How do we pass messages from our outNetMessagePool to another router
*/
public CommSystemFacade commSystem() { return _commSystem; }
/**
* Organize the peers we know about into various tiers, profiling their
* performance and sorting them accordingly.
*/
public ProfileOrganizer profileOrganizer() { return _profileOrganizer; }
/**
* Minimal interface for selecting peers for various tasks based on given
* criteria. This is kept seperate from the profile organizer since this
* logic is independent of how the peers are organized (or profiled even).
*/
public PeerManagerFacade peerManager() { return _peerManagerFacade; }
/**
* Expose a simple API for various router components to take note of
* particular events that a peer enacts (sends us a message, agrees to
* participate in a tunnel, etc).
*/
public ProfileManager profileManager() { return _profileManager; }
/**
* Coordinate this router's bandwidth limits
*/
public FIFOBandwidthLimiter bandwidthLimiter() { return _bandwidthLimiter; }
/**
* Coordinate this router's tunnels (its pools, participation, backup, etc).
* Any configuration for the tunnels is rooted from the context's properties
*/
public TunnelManagerFacade tunnelManager() { return _tunnelManager; }
/**
* Handle tunnel messages, as well as coordinate the gateways
*/
public TunnelDispatcher tunnelDispatcher() { return _tunnelDispatcher; }
/**
* If the router is configured to, gather up some particularly tasty morsels
* regarding the stats managed and offer to publish them into the routerInfo.
*/
public StatisticsManager statPublisher() { return _statPublisher; }
/**
* who does this peer hate?
*/
public Banlist banlist() { return _banlist; }
public Blocklist blocklist() { return _blocklist; }
/**
* The router keeps track of messages it receives to prevent duplicates, as
* well as other criteria for "validity".
*/
public MessageValidator messageValidator() { return _messageValidator; }
/**
* Component to coordinate our accepting/rejecting of requests under load
*
*/
public RouterThrottle throttle() { return _throttle; }
@Override
public String toString() {
StringBuilder buf = new StringBuilder(512);
buf.append("RouterContext: ").append(super.toString()).append('\n');
buf.append(_router).append('\n');
buf.append(_clientManagerFacade).append('\n');
buf.append(_clientMessagePool).append('\n');
buf.append(_jobQueue).append('\n');
buf.append(_inNetMessagePool).append('\n');
buf.append(_outNetMessagePool).append('\n');
buf.append(_messageHistory).append('\n');
buf.append(_messageRegistry).append('\n');
buf.append(_netDb).append('\n');
buf.append(_keyManager).append('\n');
buf.append(_commSystem).append('\n');
buf.append(_profileOrganizer).append('\n');
buf.append(_peerManagerFacade).append('\n');
buf.append(_profileManager).append('\n');
buf.append(_bandwidthLimiter).append('\n');
buf.append(_tunnelManager).append('\n');
buf.append(_statPublisher).append('\n');
buf.append(_banlist).append('\n');
buf.append(_messageValidator).append('\n');
return buf.toString();
}
/**
* Tie in the router's config as properties, as well as whatever the
* I2PAppContext says.
*
*/
@Override
public String getProperty(String propName) {
if (_router != null) {
String val = _router.getConfigSetting(propName);
if (val != null) return val;
}
return super.getProperty(propName);
}
/**
* Tie in the router's config as properties, as well as whatever the
* I2PAppContext says.
*
*/
@Override
public String getProperty(String propName, String defaultVal) {
if (_router != null) {
String val = _router.getConfigSetting(propName);
if (val != null) return val;
}
return super.getProperty(propName, defaultVal);
}
/**
* Return an int with an int default
*/
@Override
public int getProperty(String propName, int defaultVal) {
if (_router != null) {
String val = _router.getConfigSetting(propName);
if (val != null) {
int ival = defaultVal;
try {
ival = Integer.parseInt(val);
} catch (NumberFormatException nfe) {}
return ival;
}
}
return super.getProperty(propName, defaultVal);
}
/**
* Return a long with a long default
* @since 0.9.4
*/
@Override
public long getProperty(String propName, long defaultVal) {
if (_router != null) {
String val = _router.getConfigSetting(propName);
if (val != null) {
long rv = defaultVal;
try {
rv = Long.parseLong(val);
} catch (NumberFormatException nfe) {}
return rv;
}
}
return super.getProperty(propName, defaultVal);
}
/**
* @return new Properties with system and context properties
* @since 0.8.4
*/
@Override
public Properties getProperties() {
Properties rv = super.getProperties();
if (_router != null)
rv.putAll(_router.getConfigMap());
return rv;
}
@Override
protected void initializeClock() {
synchronized (_lock1) {
if (_clock == null) {
RouterClock rc = new RouterClock(this);
rc.start();
_clock = rc;
}
_clockInitialized = true;
}
}
/** override to support storage in router.config */
@Override
public KeyRing keyRing() {
if (!_keyRingInitialized)
initializeKeyRing();
return _keyRing;
}
@Override
protected void initializeKeyRing() {
synchronized (_lock2) {
if (_keyRing == null)
_keyRing = new PersistentKeyRing(this);
_keyRingInitialized = true;
}
}
/**
* @since 0.8.8
*/
void removeShutdownTasks() {
_shutdownTasks.clear();
}
/**
* The last thing to be called before router shutdown.
* No context resources, including logging, will be available.
* Only for external threads in the same JVM needing to know when
* the shutdown is complete, like Android.
* @since 0.8.8
*/
public void addFinalShutdownTask(Runnable task) {
_finalShutdownTasks.add(task);
}
/**
* @return the Set
* @since 0.8.8
*/
Set<Runnable> getFinalShutdownTasks() {
return _finalShutdownTasks;
}
/**
* Use this instead of context instanceof RouterContext
* @return true
* @since 0.7.9
*/
@Override
public boolean isRouterContext() {
return true;
}
/**
* Use this to connect to the router in the same JVM.
* @return the client manager
* @since 0.8.3
*/
@Override
public InternalClientManager internalClientManager() {
return _internalClientManager;
}
/**
* The RouterAppManager.
* @return the manager
* @since 0.9.4
*/
@Override
public ClientAppManager clientAppManager() {
return _appManager;
}
/**
* The RouterAppManager.
* For convenience, same as clientAppManager(), no cast required
* @return the manager
* @since 0.9.11
*/
public RouterAppManager routerAppManager() {
return _appManager;
}
/**
* As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext.
* Overridden in RouterContext to return the full TransientSessionKeyManager.
*
* @since 0.9.15
*/
@Override
protected void initializeSessionKeyManager() {
synchronized (_lock3) {
if (_sessionKeyManager == null)
//_sessionKeyManager = new PersistentSessionKeyManager(this);
_sessionKeyManager = new TransientSessionKeyManager(this);
_sessionKeyManagerInitialized = true;
}
}
/**
* Determine how much do we want to mess with the keys to turn them
* into something we can route. This is context specific because we
* may want to test out how things react when peers don't agree on
* how to skew.
*
* Returns same thing as routerKeyGenerator()
*
* @return non-null
* @since 0.9.16 Overrides I2PAppContext. Returns non-null in RouterContext and null in I2PAppcontext.
*/
@Override
public RoutingKeyGenerator routingKeyGenerator() {
return _routingKeyGenerator;
}
/**
* Determine how much do we want to mess with the keys to turn them
* into something we can route. This is context specific because we
* may want to test out how things react when peers don't agree on
* how to skew.
*
* Returns same thing as routingKeyGenerator()
*
* @return non-null
* @since 0.9.16
*/
public RouterKeyGenerator routerKeyGenerator() {
return _routingKeyGenerator;
}
/**
* Since we only need one.
*
* @return non-null after initAll()
* @since 0.9.20
*/
public GarlicMessageParser garlicMessageParser() {
return _garlicMessageParser;
}
}