From 731e26e7d68f9fbeaa7301c7cb588cd7b22fe16c Mon Sep 17 00:00:00 2001 From: jrandom Date: Tue, 18 Oct 2005 00:39:46 +0000 Subject: [PATCH] 2005-10-17 jrandom * Allow an env prop to configure whether we want to use the backwards compatible (but not standards compliant) HMAC-MD5, or whether we want to use the not-backwards compatible (but standards compliant) one. No one should touch this setting, unless your name is toad or jrandom ;) * Added some new dummy facades * Be more aggressive on loading up the router.config before building the router context * Added new hooks for apps to deal with previously undefined I2NP message types without having to modify any code. * Demo code for using a castrated router for SSU comm (SSUDemo.java) --- build.xml | 10 +- .../net/i2p/crypto/HMACSHA256Generator.java | 5 + history.txt | 14 +- .../net/i2p/data/i2np/I2NPMessageImpl.java | 18 +- router/java/src/net/i2p/router/JobQueue.java | 4 +- .../net/i2p/router/NetworkDatabaseFacade.java | 15 +- router/java/src/net/i2p/router/Router.java | 42 ++- .../src/net/i2p/router/RouterContext.java | 20 +- .../src/net/i2p/router/RouterVersion.java | 4 +- router/java/src/net/i2p/router/SSUDemo.java | 271 ++++++++++++++++++ .../net/i2p/router/TunnelManagerFacade.java | 31 ++ 11 files changed, 406 insertions(+), 28 deletions(-) create mode 100644 router/java/src/net/i2p/router/SSUDemo.java diff --git a/build.xml b/build.xml index 5c2fff5a7..cf3899795 100644 --- a/build.xml +++ b/build.xml @@ -31,6 +31,14 @@ + + + + + + + + @@ -367,8 +375,8 @@ - + diff --git a/core/java/src/net/i2p/crypto/HMACSHA256Generator.java b/core/java/src/net/i2p/crypto/HMACSHA256Generator.java index ae1399e22..b9b0109c0 100644 --- a/core/java/src/net/i2p/crypto/HMACSHA256Generator.java +++ b/core/java/src/net/i2p/crypto/HMACSHA256Generator.java @@ -28,6 +28,7 @@ public class HMACSHA256Generator { /** set of available byte[] buffers for verify */ private List _availableTmp; private boolean _useMD5; + private int _macSize; public static final boolean DEFAULT_USE_MD5 = true; @@ -39,6 +40,10 @@ public class HMACSHA256Generator { _useMD5 = true; else _useMD5 = false; + if ("true".equals(context.getProperty("i2p.HMACBrokenSize", "true"))) + _macSize = 32; + else + _macSize = (_useMD5 ? 16 : 32); } public static HMACSHA256Generator getInstance() { diff --git a/history.txt b/history.txt index 6b6b4e2c3..2c8856cd0 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,16 @@ -$Id: history.txt,v 1.297 2005/10/14 11:02:38 jrandom Exp $ +$Id: history.txt,v 1.298 2005/10/14 11:26:31 jrandom Exp $ + +2005-10-17 jrandom + * Allow an env prop to configure whether we want to use the backwards + compatible (but not standards compliant) HMAC-MD5, or whether we want + to use the not-backwards compatible (but standards compliant) one. No + one should touch this setting, unless your name is toad or jrandom ;) + * Added some new dummy facades + * Be more aggressive on loading up the router.config before building the + router context + * Added new hooks for apps to deal with previously undefined I2NP message + types without having to modify any code. + * Demo code for using a castrated router for SSU comm (SSUDemo.java) 2005-10-14 jrandom * More explicit filter for linux/PPC building (thanks anon!) diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java index 38b86ac91..b5f97c231 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java @@ -12,6 +12,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; @@ -36,6 +39,15 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM private static final boolean RAW_FULL_SIZE = false; + /** unsynchronized as its pretty much read only (except at startup) */ + private static final Map _builders = new HashMap(8); + public static final void registerBuilder(Builder builder, int type) { _builders.put(new Integer(type), builder); } + /** interface for extending the types of messages handled */ + public interface Builder { + /** instantiate a new I2NPMessage to be populated shortly */ + public I2NPMessage build(I2PAppContext ctx); + } + public I2NPMessageImpl(I2PAppContext context) { _context = context; _log = context.logManager().getLog(I2NPMessageImpl.class); @@ -334,7 +346,11 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM case TunnelCreateStatusMessage.MESSAGE_TYPE: return new TunnelCreateStatusMessage(context); default: - return null; + Builder builder = (Builder)_builders.get(new Integer(type)); + if (builder == null) + return null; + else + return builder.build(context); } } } diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index c9f4a423d..6198cfdcf 100644 --- a/router/java/src/net/i2p/router/JobQueue.java +++ b/router/java/src/net/i2p/router/JobQueue.java @@ -440,8 +440,8 @@ public class JobQueue { timeToWait = 10; else if (timeToWait > 10*1000) timeToWait = 10*1000; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Waiting " + timeToWait + " before rechecking the timed queue"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Waiting " + timeToWait + " before rechecking the timed queue"); try { _jobLock.wait(timeToWait); } catch (InterruptedException ie) {} diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java index 7d591800e..3027b66b1 100644 --- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java @@ -10,10 +10,7 @@ package net.i2p.router; import java.io.IOException; import java.io.Writer; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; @@ -68,7 +65,7 @@ class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade { private RouterContext _context; public DummyNetworkDatabaseFacade(RouterContext ctx) { - _routers = new HashMap(); + _routers = Collections.synchronizedMap(new HashMap()); _context = ctx; } @@ -93,11 +90,13 @@ class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade { public void publish(RouterInfo localRouterInfo) {} public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; } public RouterInfo store(Hash key, RouterInfo routerInfo) { - _routers.put(key, routerInfo); - return routerInfo; + RouterInfo rv = (RouterInfo)_routers.put(key, routerInfo); + return rv; } public void unpublish(LeaseSet localLeaseSet) {} - public void fail(Hash dbEntry) {} + public void fail(Hash dbEntry) { + _routers.remove(dbEntry); + } public Set findNearestRouters(Hash key, int maxNumRouters, Set peersToIgnore) { return new HashSet(_routers.values()); } diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 1acea8ae1..1702f89b8 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -106,11 +106,30 @@ public class Router { } _config = new Properties(); - _context = new RouterContext(this, envProps); - if (configFilename == null) - _configFilename = _context.getProperty(PROP_CONFIG_FILE, "router.config"); - else + + 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; + } + + readConfig(); + if (envProps == null) { + envProps = _config; + } else { + for (Iterator iter = _config.keySet().iterator(); iter.hasNext(); ) { + String k = (String)iter.next(); + String v = _config.getProperty(k); + envProps.setProperty(k, v); + } + } + + + _context = new RouterContext(this, envProps); _routerInfo = null; _higherVersionSeen = false; _log = _context.logManager().getLog(Router.class); @@ -248,9 +267,12 @@ public class Router { } private static Properties getConfig(RouterContext ctx, String filename) { - Log log = ctx.logManager().getLog(Router.class); - if (log.shouldLog(Log.DEBUG)) - log.debug("Config file: " + 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(); FileInputStream fis = null; try { @@ -260,10 +282,12 @@ public class Router { // dont be a wanker props.remove(PROP_SHUTDOWN_IN_PROGRESS); } else { - log.warn("Configuration file " + filename + " does not exist"); + if (log != null) + log.warn("Configuration file " + filename + " does not exist"); } } catch (Exception ioe) { - log.error("Error loading the router configuration from " + filename, ioe); + if (log != null) + log.error("Error loading the router configuration from " + filename, ioe); } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) {} } diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index a5437d959..400213517 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -90,7 +90,10 @@ public class RouterContext extends I2PAppContext { } private void initAll() { _adminManager = new AdminManager(this); - _clientManagerFacade = new ClientManagerFacadeImpl(this); + if ("false".equals(getProperty("i2p.dummyClientFacade", "false"))) + _clientManagerFacade = new ClientManagerFacadeImpl(this); + else + _clientManagerFacade = new DummyClientManagerFacade(this); _clientMessagePool = new ClientMessagePool(this); _jobQueue = new JobQueue(this); _inNetMessagePool = new InNetMessagePool(this); @@ -98,17 +101,26 @@ public class RouterContext extends I2PAppContext { _messageHistory = new MessageHistory(this); _messageRegistry = new OutboundMessageRegistry(this); _messageStateMonitor = new MessageStateMonitor(this); - _netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this); + if ("false".equals(getProperty("i2p.dummyNetDb", "false"))) + _netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this); + else + _netDb = new DummyNetworkDatabaseFacade(this); _keyManager = new KeyManager(this); if ("false".equals(getProperty("i2p.vmCommSystem", "false"))) _commSystem = new CommSystemFacadeImpl(this); else _commSystem = new VMCommSystem(this); _profileOrganizer = new ProfileOrganizer(this); - _peerManagerFacade = new PeerManagerFacadeImpl(this); + if ("false".equals(getProperty("i2p.dummyPeerManager", "false"))) + _peerManagerFacade = new PeerManagerFacadeImpl(this); + else + _peerManagerFacade = new DummyPeerManagerFacade(); _profileManager = new ProfileManagerImpl(this); _bandwidthLimiter = new FIFOBandwidthLimiter(this); - _tunnelManager = new TunnelPoolManager(this); + if ("false".equals(getProperty("i2p.dummyTunnelManager", "false"))) + _tunnelManager = new TunnelPoolManager(this); + else + _tunnelManager = new DummyTunnelManagerFacade(); _tunnelDispatcher = new TunnelDispatcher(this); _statPublisher = new StatisticsManager(this); _shitlist = new Shitlist(this); diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 95d4fd6f4..89adea0ea 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.269 $ $Date: 2005/10/13 21:15:40 $"; + public final static String ID = "$Revision: 1.270 $ $Date: 2005/10/14 08:48:05 $"; public final static String VERSION = "0.6.1.3"; - public final static long BUILD = 0; + public final static long BUILD = 1; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/SSUDemo.java b/router/java/src/net/i2p/router/SSUDemo.java new file mode 100644 index 000000000..00c848e8d --- /dev/null +++ b/router/java/src/net/i2p/router/SSUDemo.java @@ -0,0 +1,271 @@ +package net.i2p.router; + +import java.util.*; +import java.io.*; + +import net.i2p.I2PAppContext; +import net.i2p.data.*; +import net.i2p.data.i2np.*; + +/** + * Demo of a stripped down router - no tunnels, no netDb, no i2cp, no peer profiling, + * just the SSU comm layer, crypto, and associated infrastructure, extended to handle + * a new type of message ("FooMessage"). + * + */ +public class SSUDemo { + RouterContext _us; + + public static void main(String args[]) { + SSUDemo demo = new SSUDemo(); + demo.run(); + } + + public SSUDemo() {} + public void run() { + String cfgFile = "router.config"; + Properties envProps = getEnv(); + Router r = new Router(cfgFile, envProps); + r.runRouter(); + _us = r.getContext(); + setupHandlers(); + // wait for it to warm up a bit + try { Thread.sleep(30*1000); } catch (InterruptedException ie) {} + // now write out our ident and info + RouterInfo myInfo = _us.router().getRouterInfo(); + storeMyInfo(myInfo); + // look for any other peers written to the same directory, and send each + // a single Foo message (0x0123), unless they've already contacted us first. + // this call never returns + loadPeers(); + } + + private Properties getEnv() { + Properties envProps = System.getProperties(); + // disable the TCP transport, as its deprecated + envProps.setProperty("i2np.tcp.disable", "true"); + // we want SNTP synchronization for replay prevention + envProps.setProperty("time.disabled", "false"); + // allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false, + // peers who say they're on an invalid IP are shitlisted + envProps.setProperty("i2np.udp.allowLocal", "true"); + // explicit IP+port. at least one router on the net has to have their IP+port + // set, since there has to be someone to detect one's IP off. most don't need + // to set these though + envProps.setProperty("i2np.udp.host", "127.0.0.1"); + envProps.setProperty("i2np.udp.internalPort", "12000"); + envProps.setProperty("i2np.udp.port", "12000"); + // disable I2CP, the netDb, peer testing/profile persistence, and tunnel + // creation/management + envProps.setProperty("i2p.dummyClientFacade", "true"); + envProps.setProperty("i2p.dummyNetDb", "true"); + envProps.setProperty("i2p.dummyPeerManager", "true"); + envProps.setProperty("i2p.dummyTunnelManager", "true"); + // set to false if you want to use HMAC-SHA256-128 instead of HMAC-MD5-128 as + // the SSU MAC + envProps.setProperty("i2p.HMACMD5", "true"); + // if you're using the HMAC MD5, by default it will use a 32 byte MAC field, + // which is a bug, as it doesn't generate the same values as a 16 byte MAC field. + // set this to false if you don't want the bug + envProps.setProperty("i2p.HMACBrokenSize", "false"); + // no need to include any stats in the routerInfo we send to people on SSU + // session establishment + envProps.setProperty("router.publishPeerRankings", "false"); + // write the logs to ./logs/log-router-*.txt (logger configured with the file + // ./logger.config, or another config file specified as + // -Dlogger.configLocation=blah) + envProps.setProperty("loggerFilenameOverride", "logs/log-router-@.txt"); + return envProps; + } + + private void setupHandlers() { + // netDb store is sent on connection establishment, which includes contact info + // for the peer. the DBStoreJobBuilder builds a new asynchronous Job to process + // each one received (storing it in our in-memory, passive netDb) + _us.inNetMessagePool().registerHandlerJobBuilder(DatabaseStoreMessage.MESSAGE_TYPE, new DBStoreJobBuilder()); + // handle any Foo messages by displaying them on stdout + _us.inNetMessagePool().registerHandlerJobBuilder(FooMessage.MESSAGE_TYPE, new FooJobBuilder()); + } + + /** random place for storing router info files - written as $dir/base64(SHA256(info.getIdentity)) */ + private File getInfoDir() { return new File("/tmp/ssuDemoInfo/"); } + + private void storeMyInfo(RouterInfo info) { + File infoDir = getInfoDir(); + if (!infoDir.exists()) + infoDir.mkdirs(); + FileOutputStream fos = null; + File infoFile = new File(infoDir, info.getIdentity().calculateHash().toBase64()); + try { + fos = new FileOutputStream(infoFile); + info.writeBytes(fos); + } catch (IOException ioe) { + ioe.printStackTrace(); + } catch (DataFormatException dfe) { + dfe.printStackTrace(); + } finally { + if (fos != null) try { fos.close(); } catch (IOException ioe) {} + } + + System.out.println("Our info stored at: " + infoFile.getAbsolutePath()); + } + + private void loadPeers() { + File infoDir = getInfoDir(); + if (!infoDir.exists()) + infoDir.mkdirs(); + while (true) { + File peerFiles[] = infoDir.listFiles(); + if ( (peerFiles != null) && (peerFiles.length > 0) ) { + for (int i = 0; i < peerFiles.length; i++) { + if (peerFiles[i].isFile() && !peerFiles[i].isHidden()) { + if (!_us.routerHash().toBase64().equals(peerFiles[i].getName())) { + System.out.println("Reading info: " + peerFiles[i].getAbsolutePath()); + try { + FileInputStream in = new FileInputStream(peerFiles[i]); + RouterInfo ri = new RouterInfo(); + ri.readBytes(in); + peerRead(ri); + } catch (IOException ioe) { + System.err.println("Error reading " + peerFiles[i].getAbsolutePath()); + ioe.printStackTrace(); + } catch (DataFormatException dfe) { + System.err.println("Corrupt " + peerFiles[i].getAbsolutePath()); + dfe.printStackTrace(); + } + } + } + } + } + try { Thread.sleep(30*1000); } catch (InterruptedException ie) {} + } + } + + private void peerRead(RouterInfo ri) { + RouterInfo old = _us.netDb().store(ri.getIdentity().calculateHash(), ri); + if (old == null) + newPeerRead(ri); + } + + private void newPeerRead(RouterInfo ri) { + OutNetMessage out = new OutNetMessage(_us); + // _us.clock() is an ntp synchronized clock. give up on sending this message + // if it doesn't get ACKed within the next 10 seconds + out.setExpiration(_us.clock().now() + 10*1000); + out.setPriority(100); + out.setTarget(ri); + FooMessage data = new FooMessage(_us, new byte[] { 0x0, 0x1, 0x2, 0x3 }); + System.out.println("SEND: " + Base64.encode(data.getData())); + out.setMessage(data); + // job fired if we can't contact them, or if it takes too long to get an ACK + out.setOnFailedSendJob(null); + // job fired once the transport gets a full ACK of the message + out.setOnSendJob(new AfterACK()); + // queue up the message, establishing a new SSU session if necessary, using + // their direct SSU address if they have one, or their indirect SSU addresses + // if they don't. If we cannot contact them, we will 'shitlist' their address, + // during which time we will not even attempt to send messages to them. We also + // drop their netDb info when we shitlist them, in case their info is no longer + // correct. Since the netDb is disabled for all meaningful purposes, the SSUDemo + // will be responsible for fetching such information. + _us.outNetMessagePool().add(out); + } + + /** fired if and only if the FooMessage is ACKed before we time out */ + private class AfterACK extends JobImpl { + public AfterACK() { super(_us); } + public void runJob() { System.out.println("Foo message sent completely"); } + public String getName() { return "After Foo message send"; } + } + + //// + // Foo and netDb store handling below + + /** + * Deal with an Foo message received + */ + private class FooJobBuilder implements HandlerJobBuilder { + public FooJobBuilder() { + I2NPMessageImpl.registerBuilder(new FooBuilder(), FooMessage.MESSAGE_TYPE); + } + public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { + return new FooHandleJob(_us, receivedMessage, from, fromHash); + } + } + private class FooHandleJob extends JobImpl { + private I2NPMessage _msg; + public FooHandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { + super(ctx); + _msg = receivedMessage; + } + public void runJob() { + // we know its a FooMessage, since thats the type of message that the handler + // is registered as + FooMessage m = (FooMessage)_msg; + System.out.println("RECV: " + Base64.encode(m.getData())); + } + public String getName() { return "Handle Foo message"; } + } + private class FooBuilder implements I2NPMessageImpl.Builder { + public I2NPMessage build(I2PAppContext ctx) { return new FooMessage(ctx, null); } + } + + /** + * Just carry some data... + */ + class FooMessage extends I2NPMessageImpl { + private byte[] _data; + public static final int MESSAGE_TYPE = 17; + public FooMessage(I2PAppContext ctx, byte data[]) { + super(ctx); + _data = data; + } + /** pull the read data off */ + public byte[] getData() { return _data; } + /** specify the payload to be sent */ + public void setData(byte data[]) { _data = data; } + + public int getType() { return MESSAGE_TYPE; } + protected int calculateWrittenLength() { return _data.length; } + public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException { + _data = new byte[dataSize]; + System.arraycopy(data, offset, _data, 0, dataSize); + } + + protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException { + System.arraycopy(_data, 0, out, curIndex, _data.length); + return curIndex + _data.length; + } + } + + //// + // netDb store handling below + + /** + * Handle any netDb stores from the peer - they send us their netDb as part of + * their SSU establishment (and we send them ours). + */ + private class DBStoreJobBuilder implements HandlerJobBuilder { + public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { + return new HandleJob(_us, receivedMessage, from, fromHash); + } + } + private class HandleJob extends JobImpl { + private I2NPMessage _msg; + public HandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { + super(ctx); + _msg = receivedMessage; + } + public void runJob() { + // we know its a DatabaseStoreMessage, since thats the type of message that the handler + // is registered as + DatabaseStoreMessage m = (DatabaseStoreMessage)_msg; + try { + _us.netDb().store(m.getKey(), m.getRouterInfo()); + } catch (IllegalArgumentException iae) { + iae.printStackTrace(); + } + } + public String getName() { return "Handle netDb store"; } + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/TunnelManagerFacade.java b/router/java/src/net/i2p/router/TunnelManagerFacade.java index a6a6c5b18..67a2c4033 100644 --- a/router/java/src/net/i2p/router/TunnelManagerFacade.java +++ b/router/java/src/net/i2p/router/TunnelManagerFacade.java @@ -12,6 +12,9 @@ import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.TunnelId; +import java.io.IOException; +import java.io.Writer; + /** * Build and maintain tunnels throughout the network. * @@ -66,3 +69,31 @@ public interface TunnelManagerFacade extends Service { public void setInboundSettings(Hash client, TunnelPoolSettings settings); public void setOutboundSettings(Hash client, TunnelPoolSettings settings); } + +class DummyTunnelManagerFacade implements TunnelManagerFacade { + + public TunnelInfo getTunnelInfo(TunnelId id) { return null; } + public TunnelInfo selectInboundTunnel() { return null; } + public TunnelInfo selectInboundTunnel(Hash destination) { return null; } + public TunnelInfo selectOutboundTunnel() { return null; } + public TunnelInfo selectOutboundTunnel(Hash destination) { return null; } + public boolean isInUse(Hash peer) { return false; } + public int getParticipatingCount() { return 0; } + public int getFreeTunnelCount() { return 0; } + public int getOutboundTunnelCount() { return 0; } + public long getLastParticipatingExpiration() { return -1; } + public void buildTunnels(Destination client, ClientTunnelSettings settings) {} + public TunnelPoolSettings getInboundSettings() { return null; } + public TunnelPoolSettings getOutboundSettings() { return null; } + public TunnelPoolSettings getInboundSettings(Hash client) { return null; } + public TunnelPoolSettings getOutboundSettings(Hash client) { return null; } + public void setInboundSettings(TunnelPoolSettings settings) {} + public void setOutboundSettings(TunnelPoolSettings settings) {} + public void setInboundSettings(Hash client, TunnelPoolSettings settings) {} + public void setOutboundSettings(Hash client, TunnelPoolSettings settings) {} + + public void renderStatusHTML(Writer out) throws IOException {} + public void restart() {} + public void shutdown() {} + public void startup() {} +}