From 9089fdd2d5a47d430e414e0e85b0721171269a1c Mon Sep 17 00:00:00 2001 From: jrandom Date: Sat, 26 Nov 2005 09:16:11 +0000 Subject: [PATCH] 2005-11-26 Raccoon23 * Added support for 'dynamic keys' mode, where the router creates a new router identity whenever it detects a substantial change in its public address (read: SSU IP or port). This only offers minimal additional protection against trivial attackers, but should provide functional improvement for people who have periodic IP changes, since their new router address would not be shitlisted while their old one would be. * Added further infrastructure for restricted route operation, but its use is not recommended. --- .../net/i2p/router/web/ConfigNetHandler.java | 36 ++++++++ .../net/i2p/router/web/ConfigNetHelper.java | 17 ++++ .../i2p/router/web/ConfigServiceHandler.java | 25 +++++- apps/routerconsole/jsp/config.jsp | 17 ++++ core/java/src/net/i2p/data/RouterInfo.java | 51 ++++++++++-- history.txt | 12 ++- router/java/src/net/i2p/router/Router.java | 82 +++++++++++++++++-- .../src/net/i2p/router/RouterVersion.java | 4 +- router/java/src/net/i2p/router/Shitlist.java | 2 +- .../HandleDatabaseLookupMessageJob.java | 27 ++++++ .../HandleDatabaseStoreMessageJob.java | 9 ++ .../networkdb/PublishLocalRouterInfoJob.java | 4 + .../FloodfillNetworkDatabaseFacade.java | 1 + ...andleFloodfillDatabaseStoreMessageJob.java | 2 + .../KademliaNetworkDatabaseFacade.java | 3 +- .../router/networkdb/kademlia/SearchJob.java | 3 +- .../router/networkdb/kademlia/StoreJob.java | 6 +- .../router/startup/CreateRouterInfoJob.java | 6 +- .../i2p/router/startup/LoadRouterInfoJob.java | 3 + .../router/startup/RebuildRouterInfoJob.java | 5 ++ .../router/transport/udp/UDPTransport.java | 18 +++- .../pool/HandleTunnelCreateMessageJob.java | 6 ++ .../tunnel/pool/TunnelPeerSelector.java | 7 +- 23 files changed, 319 insertions(+), 27 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java index c3641d40f..1c1d3147b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java @@ -17,6 +17,10 @@ import java.util.Set; import net.i2p.time.Timestamper; import net.i2p.router.transport.udp.UDPTransport; +import net.i2p.router.Router; +import net.i2p.data.RouterInfo; +import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerTask; +import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerAndRekeyTask; /** * Handler to deal with form submissions from the main config form and act @@ -31,6 +35,8 @@ public class ConfigNetHandler extends FormHandler { private boolean _recheckReachabilityRequested; private boolean _timeSyncEnabled; private boolean _requireIntroductions; + private boolean _hiddenMode; + private boolean _dynamicKeys; private String _tcpPort; private String _udpPort; private String _inboundRate; @@ -62,6 +68,8 @@ public class ConfigNetHandler extends FormHandler { public void setEnabletimesync(String moo) { _timeSyncEnabled = true; } public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; } public void setRequireIntroductions(String moo) { _requireIntroductions = true; } + public void setHiddenMode(String moo) { _hiddenMode = true; } + public void setDynamicKeys(String moo) { _dynamicKeys = true; } public void setHostname(String hostname) { _hostname = (hostname != null ? hostname.trim() : null); @@ -263,6 +271,28 @@ public class ConfigNetHandler extends FormHandler { addFormNotice("Updating bandwidth share percentage"); } } + + // If hidden mode value changes, restart is required + if (_hiddenMode && "false".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) { + _context.router().setConfigSetting(Router.PROP_HIDDEN, "true"); + _context.router().getRouterInfo().addCapability(RouterInfo.CAPABILITY_HIDDEN); + addFormNotice("Gracefully restarting into Hidden Router Mode. Make sure you have no 0-1 length " + + "tunnels!"); + hiddenSwitch(); + } + + if (!_hiddenMode && "true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) { + _context.router().removeConfigSetting(Router.PROP_HIDDEN); + _context.router().getRouterInfo().delCapability(RouterInfo.CAPABILITY_HIDDEN); + addFormNotice("Gracefully restarting to exit Hidden Router Mode"); + hiddenSwitch(); + } + + if (_dynamicKeys) { + _context.router().setConfigSetting(Router.PROP_DYNAMIC_KEYS, "true"); + } else { + _context.router().removeConfigSetting(Router.PROP_DYNAMIC_KEYS); + } if (_requireIntroductions) { _context.router().setConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS, "true"); @@ -290,6 +320,12 @@ public class ConfigNetHandler extends FormHandler { addFormNotice("Soft restart complete"); } } + + private void hiddenSwitch() { + // Full restart required to generate new keys + _context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART)); + _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); + } private void updateRates() { boolean updated = false; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index 562d07b3a..139026ec7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -6,6 +6,7 @@ import net.i2p.router.CommSystemFacade; import net.i2p.data.RouterAddress; import net.i2p.router.transport.udp.UDPAddress; import net.i2p.router.transport.udp.UDPTransport; +import net.i2p.router.Router; public class ConfigNetHelper { private RouterContext _context; @@ -63,6 +64,22 @@ public class ConfigNetHelper { return " checked "; } + public String getHiddenModeChecked() { + String enabled = _context.getProperty(Router.PROP_HIDDEN, "false"); + if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) ) + return " checked "; + else + return ""; + } + + public String getDynamicKeysChecked() { + String enabled = _context.getProperty(Router.PROP_DYNAMIC_KEYS, "false"); + if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) ) + return " checked "; + else + return ""; + } + public String getRequireIntroductionsChecked() { short status = _context.commSystem().getReachabilityStatus(); switch (status) { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java index 561f8bebe..197f45db7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java @@ -33,6 +33,21 @@ public class ConfigServiceHandler extends FormHandler { } } } + + public static class UpdateWrapperManagerAndRekeyTask implements Runnable { + private int _exitCode; + public UpdateWrapperManagerAndRekeyTask(int exitCode) { + _exitCode = exitCode; + } + public void run() { + try { + Router.killKeys(); + WrapperManager.signalStopped(_exitCode); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } protected void processForm() { if (_action == null) return; @@ -56,6 +71,14 @@ public class ConfigServiceHandler extends FormHandler { _context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART)); _context.router().shutdown(Router.EXIT_HARD_RESTART); addFormNotice("Hard restart requested"); + } else if ("Rekey and Restart".equals(_action)) { + addFormNotice("Rekeying after graceful restart"); + _context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART)); + _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); + } else if ("Rekey and Shutdown".equals(_action)) { + addFormNotice("Rekeying after graceful shutdown"); + _context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL)); + _context.router().shutdownGracefully(Router.EXIT_GRACEFUL); } else if ("Run I2P on startup".equals(_action)) { installService(); } else if ("Don't run I2P on startup".equals(_action)) { @@ -195,4 +218,4 @@ public class ConfigServiceHandler extends FormHandler { addFormError("Error updating the client config"); } } -} \ No newline at end of file +} diff --git a/apps/routerconsole/jsp/config.jsp b/apps/routerconsole/jsp/config.jsp index 3cd245558..6f0c21e2b 100644 --- a/apps/routerconsole/jsp/config.jsp +++ b/apps/routerconsole/jsp/config.jsp @@ -58,6 +58,23 @@
Sharing a higher percentage will improve your anonymity and help the network
+ Dynamic Router Keys: + />
+

+ This setting causes your router identity to be regenerated every time your IP address + changes. If you have a dynamic IP this option can speed up your reintegration into + the network (since people will have shitlisted your old router identity), and, for + very weak adversaries, help frustrate trivial + intersection + attacks against the NetDB. Your different router identities would only be + 'hidden' among other I2P users at your ISP, and further analysis would link + the router identities further.

+

Note that when I2P detects an IP address change, it will automatically + initiate a restart in order to rekey and to disconnect from peers before they + update their profiles - any long lasting client connections will be disconnected, + though such would likely already be the case anyway, since the IP address changed. +

+

diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index d27434710..a875759c3 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -51,6 +51,8 @@ public class RouterInfo extends DataStructureImpl { public static final String PROP_NETWORK_ID = "netId"; public static final String PROP_CAPABILITIES = "caps"; + public static final char CAPABILITY_HIDDEN = 'H'; + public RouterInfo() { setIdentity(null); @@ -250,12 +252,21 @@ public class RouterInfo extends DataStructureImpl { try { _identity.writeBytes(out); DataHelper.writeDate(out, new Date(_published)); - DataHelper.writeLong(out, 1, _addresses.size()); - List addresses = DataHelper.sortStructures(_addresses); - for (Iterator iter = addresses.iterator(); iter.hasNext();) { - RouterAddress addr = (RouterAddress) iter.next(); - addr.writeBytes(out); + if (isHidden()) { + // Do not send IP address to peers in hidden mode + DataHelper.writeLong(out, 1, 0); + } else { + DataHelper.writeLong(out, 1, _addresses.size()); + List addresses = DataHelper.sortStructures(_addresses); + for (Iterator iter = addresses.iterator(); iter.hasNext();) { + RouterAddress addr = (RouterAddress) iter.next(); + addr.writeBytes(out); + } } + // XXX: what about peers? + // answer: they're always empty... they're a placeholder for one particular + // method of trusted links, which isn't implemented in the router + // at the moment, and may not be later. DataHelper.writeLong(out, 1, _peers.size()); List peers = DataHelper.sortStructures(_peers); for (Iterator iter = peers.iterator(); iter.hasNext();) { @@ -315,7 +326,14 @@ public class RouterInfo extends DataStructureImpl { else return ""; } - + + /** + * Is this a hidden node? + */ + public boolean isHidden() { + return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1); + } + public void addCapability(char cap) { if (_options == null) _options = new OrderedProperties(); synchronized (_options) { @@ -326,6 +344,25 @@ public class RouterInfo extends DataStructureImpl { _options.setProperty(PROP_CAPABILITIES, caps + cap); } } + + public void delCapability(char cap) { + if (_options == null) return; + synchronized (_options) { + String caps = _options.getProperty(PROP_CAPABILITIES); + int idx; + if (caps == null) { + return; + } else if ((idx = caps.indexOf(cap)) == -1) { + return; + } else { + StringBuffer buf = new StringBuffer(caps); + while ( (idx = buf.indexOf(""+cap)) != -1) + buf.deleteCharAt(idx); + _options.setProperty(PROP_CAPABILITIES, buf.toString()); + } + } + } + /** * Get the routing key for the structure using the current modifier in the RoutingKeyGenerator. @@ -516,4 +553,4 @@ public class RouterInfo extends DataStructureImpl { _stringified = buf.toString(); return _stringified; } -} \ No newline at end of file +} diff --git a/history.txt b/history.txt index 3777ba7f8..ef65d315c 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,14 @@ -$Id: history.txt,v 1.329 2005/11/25 06:06:03 jrandom Exp $ +$Id: history.txt,v 1.330 2005/11/26 00:05:54 jrandom Exp $ + +2005-11-26 Raccoon23 + * Added support for 'dynamic keys' mode, where the router creates a new + router identity whenever it detects a substantial change in its public + address (read: SSU IP or port). This only offers minimal additional + protection against trivial attackers, but should provide functional + improvement for people who have periodic IP changes, since their new + router address would not be shitlisted while their old one would be. + * Added further infrastructure for restricted route operation, but its use + is not recommended. 2005-11-25 jrandom * Further Syndie UI cleanups diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 02afd4e68..c9d3baf68 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.Writer; import java.text.DecimalFormat; import java.util.Calendar; +import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashSet; @@ -29,6 +30,7 @@ import net.i2p.CoreVersion; import net.i2p.crypto.DHSessionKeyBuilder; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.data.RouterAddress; import net.i2p.data.RouterInfo; import net.i2p.data.SigningPrivateKey; import net.i2p.data.i2np.GarlicMessage; @@ -36,6 +38,8 @@ import net.i2p.data.i2np.GarlicMessage; import net.i2p.router.message.GarlicMessageHandler; //import net.i2p.router.message.TunnelMessageHandler; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import net.i2p.router.transport.udp.UDPTransport; +import net.i2p.router.transport.udp.UDPAddress; import net.i2p.router.startup.StartupJob; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; @@ -73,6 +77,8 @@ public class Router { /** used to differentiate routerInfo files on different networks */ public static final int NETWORK_ID = 1; + public final static String PROP_HIDDEN = "router.hiddenMode"; + public final static String PROP_DYNAMIC_KEYS = "router.dynamicKeys"; public final static String PROP_INFO_FILENAME = "router.info.location"; public final static String PROP_INFO_FILENAME_DEFAULT = "router.info"; public final static String PROP_KEYS_FILENAME = "router.keys.location"; @@ -211,6 +217,51 @@ public class Router { if (info != null) _context.jobQueue().addJob(new PersistRouterInfoJob()); } + + /** + * Called when our RouterInfo is loaded by LoadRouterInfoJob + * to store our most recently known address to determine if + * it has changed while we were down. + */ + public boolean updateExternalAddress(Collection addrs, boolean reboot) { + if ("false".equalsIgnoreCase(_context.getProperty(Router.PROP_DYNAMIC_KEYS, "false"))) + return false; // no one cares. pretend it didn't change + boolean ret = false; + for (Iterator i = addrs.iterator(); i.hasNext(); ) { + RouterAddress addr = (RouterAddress)i.next(); + if (UDPTransport.STYLE.equalsIgnoreCase(addr.getTransportStyle())) + ret = updateExternalAddress(addr, reboot); + } + return ret; + } + + /** + * Called by TransportImpl.replaceAddress to notify the router of an + * address change. It is the caller's responsibility to make sure this + * really is a substantial change. + * + */ + public boolean updateExternalAddress(RouterAddress addr, boolean rebootRouter) { + String newExternal = null; + // TCP is often incorrectly initialized to 83.246.74.28 for some + // reason. Numerous hosts in the netdb report this address for TCP. + // It is also easier to lie over the TCP transport. So only trust UDP. + if (!UDPTransport.STYLE.equalsIgnoreCase(addr.getTransportStyle())) + return false; + + if ("false".equalsIgnoreCase(_context.getProperty(Router.PROP_DYNAMIC_KEYS, "false"))) + return false; // no one cares. pretend it didn't change + + if (_log.shouldLog(Log.WARN)) + _log.warn("Rekeying and restarting due to " + addr.getTransportStyle() + + " address update. new address: " + addr); + if (rebootRouter) { + _context.router().rebuildNewIdentity(); + } else { + _context.router().killKeys(); + } + return true; + } /** * True if the router has tried to communicate with another router who is running a higher @@ -237,6 +288,9 @@ public class Router { readConfig(); setupHandlers(); + if ("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) + killKeys(); + _context.messageValidator().startup(); _context.tunnelDispatcher().startup(); _context.inNetMessagePool().startup(); @@ -319,6 +373,10 @@ public class Router { ri.setAddresses(_context.commSystem().createAddresses()); if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context)) ri.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL); + if("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) { + ri.addCapability(RouterInfo.CAPABILITY_HIDDEN); + } + addReachabilityCapability(ri); SigningPrivateKey key = _context.keyManager().getSigningPrivateKey(); if (key == null) { @@ -374,18 +432,15 @@ public class Router { */ private static final String _rebuildFiles[] = new String[] { "router.info", "router.keys", + "netDb/my.info", "connectionTag.keys", "keyBackup/privateEncryption.key", "keyBackup/privateSigning.key", "keyBackup/publicEncryption.key", "keyBackup/publicSigning.key", "sessionKeys.dat" }; - /** - * Rebuild a new identity the hard way - delete all of our old identity - * files, then reboot the router. - * - */ - public void rebuildNewIdentity() { + + public static void killKeys() { for (int i = 0; i < _rebuildFiles.length; i++) { File f = new File(_rebuildFiles[i]); if (f.exists()) { @@ -396,9 +451,16 @@ public class Router { System.out.println("ERROR: Could not remove old identity file: " + _rebuildFiles[i]); } } - System.out.println("INFO: Restarting the router after removing any old identity files"); + } + /** + * Rebuild a new identity the hard way - delete all of our old identity + * files, then reboot the router. + * + */ + public void rebuildNewIdentity() { + killKeys(); // hard and ugly - System.exit(EXIT_HARD_RESTART); + finalShutdown(EXIT_HARD_RESTART); } /** @@ -802,6 +864,10 @@ public class Router { } catch (Throwable t) { _log.log(Log.CRIT, "Error running shutdown task", t); } + finalShutdown(exitCode); + } + + public void finalShutdown(int exitCode) { _log.log(Log.CRIT, "Shutdown(" + exitCode + ") complete", new Exception("Shutdown")); try { _context.logManager().shutdown(); } catch (Throwable t) { } File f = new File(getPingFile()); diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 475ef4453..9fc26a328 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.297 $ $Date: 2005/11/25 06:06:02 $"; + public final static String ID = "$Revision: 1.298 $ $Date: 2005/11/26 00:05:53 $"; public final static String VERSION = "0.6.1.5"; - public final static long BUILD = 8; + public final static long BUILD = 9; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/Shitlist.java b/router/java/src/net/i2p/router/Shitlist.java index 8dbc1c1f5..9591f4450 100644 --- a/router/java/src/net/i2p/router/Shitlist.java +++ b/router/java/src/net/i2p/router/Shitlist.java @@ -61,7 +61,7 @@ public class Shitlist { } boolean wasAlready = false; if (_log.shouldLog(Log.INFO)) - _log.info("Shitlisting router " + peer.toBase64(), new Exception("Shitlist cause")); + _log.info("Shitlisting router " + peer.toBase64(), new Exception("Shitlist cause: " + reason)); long period = SHITLIST_DURATION_MS + _context.random().nextLong(SHITLIST_DURATION_MS); PeerProfile prof = _context.profileOrganizer().getProfile(peer); diff --git a/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java b/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java index 6887a9716..0bcb00aa2 100644 --- a/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java @@ -82,6 +82,15 @@ public class HandleDatabaseLookupMessageJob extends JobImpl { + " (tunnel " + _message.getReplyTunnel() + ")"); } + // If we are hidden we should not get queries, log and return + if (getContext().router().getRouterInfo().isHidden()) { + if (_log.shouldLog(Log.ERROR)) { + _log.error("Uninvited dbLookup received with replies going to " + fromKey + + " (tunnel " + _message.getReplyTunnel() + ")"); + } + return; + } + LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_message.getSearchKey()); if (ls != null) { boolean publish = getContext().clientManager().shouldPublishLeaseSet(_message.getSearchKey()); @@ -117,6 +126,14 @@ public class HandleDatabaseLookupMessageJob extends JobImpl { Set us = new HashSet(1); us.add(getContext().router().getRouterInfo()); sendClosest(_message.getSearchKey(), us, fromKey, _message.getReplyTunnel()); + //} else if (info.isHidden()) { + // // Don't return hidden nodes + // ERR: we don't want to explicitly reject lookups for hidden nodes, since they + // may have just sent the hidden mode to only us and bundled a lookup with + // a payload targetting some hidden destination (and if we refused to answer, + // yet answered the bundled data message [e.g. HTTP GET], they'd know that + // *we* were hosting that destination). To operate safely, + // perhaps we should refuse to honor lookups bundled down client tunnels? } else { // send that routerInfo to the _message.getFromHash peer if (_log.shouldLog(Log.DEBUG)) @@ -129,6 +146,16 @@ public class HandleDatabaseLookupMessageJob extends JobImpl { Set routerInfoSet = getContext().netDb().findNearestRouters(_message.getSearchKey(), MAX_ROUTERS_RETURNED, _message.getDontIncludePeers()); + + // ERR: see above + // // Remove hidden nodes from set.. + // for (Iterator iter = routerInfoSet.iterator(); iter.hasNext();) { + // RouterInfo peer = (RouterInfo)iter.next(); + // if (peer.isHidden()) { + // iter.remove(); + // } + // } + if (_log.shouldLog(Log.DEBUG)) _log.debug("We do not have key " + _message.getSearchKey().toBase64() + " locally. sending back " + routerInfoSet.size() + " peers to " + fromKey.toBase64()); diff --git a/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java b/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java index 0af50691f..d0fd57ad1 100644 --- a/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java @@ -47,6 +47,15 @@ public class HandleDatabaseStoreMessageJob extends JobImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug("Handling database store message"); + // ERR: see comments regarding hidden mode in HandleDatabaseLookupMessageJob. + // // If we are a hidden peer, log and return + // if (getContext().router().getRouterInfo().isHidden()) { + // if (_log.shouldLog(Log.ERROR)) { + // _log.error("Uninvited dbStore received (tunnel " + _message.getReplyTunnel() + ")"); + // } + // return; + // } + String invalidMessage = null; boolean wasNew = false; if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) { diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java index 0bfcf8904..6311b55f1 100644 --- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java +++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java @@ -47,6 +47,10 @@ public class PublishLocalRouterInfoJob extends JobImpl { ri.setAddresses(getContext().commSystem().createAddresses()); if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) ri.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL); + + if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false"))) + ri.addCapability(RouterInfo.CAPABILITY_HIDDEN); + getContext().router().addReachabilityCapability(ri); SigningPrivateKey key = getContext().keyManager().getSigningPrivateKey(); if (key == null) { diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java index b9c05f237..182face09 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -31,6 +31,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad */ public void publish(RouterInfo localRouterInfo) throws IllegalArgumentException { if (localRouterInfo == null) throw new IllegalArgumentException("wtf, null localRouterInfo?"); + if (localRouterInfo.isHidden()) return; // DE-nied! super.publish(localRouterInfo); sendStore(localRouterInfo.getIdentity().calculateHash(), localRouterInfo, null, null, PUBLISH_TIMEOUT, null); } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java index 22923c57f..22f2ffe99 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java @@ -95,6 +95,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()) && (_message.getReplyToken() > 0) ) { if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) _facade.flood(_message.getLeaseSet()); + // ERR: see comment in HandleDatabaseLookupMessageJob regarding hidden mode + //else if (!_message.getRouterInfo().isHidden()) else _facade.flood(_message.getRouterInfo()); } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index c53f3daee..484b46c76 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -504,12 +504,13 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { */ public void publish(RouterInfo localRouterInfo) throws IllegalArgumentException { if (!_initialized) return; + writeMyInfo(localRouterInfo); + if (localRouterInfo.isHidden()) return; // DE-nied! Hash h = localRouterInfo.getIdentity().getHash(); store(h, localRouterInfo); synchronized (_explicitSendKeys) { _explicitSendKeys.add(h); } - writeMyInfo(localRouterInfo); } /** diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java index 680872c8c..36c27ee0d 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java @@ -243,7 +243,8 @@ class SearchJob extends JobImpl { + peer + " : " + (ds == null ? "null" : ds.getClass().getName())); _state.replyTimeout(peer); } else { - if (getContext().shitlist().isShitlisted(peer)) { + if (((RouterInfo)ds).isHidden() || + getContext().shitlist().isShitlisted(peer)) { // dont bother } else { _state.addPending(peer); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java index aaccb6139..0bc7c8183 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java @@ -176,6 +176,10 @@ class StoreJob extends JobImpl { //if (getContext().shitlist().isShitlisted(((RouterInfo)ds).getIdentity().calculateHash())) { // _state.addSkipped(peer); //} else { + // + // ERR: see hidden mode comments in HandleDatabaseLookupMessageJob + // // Do not store to hidden nodes + // if (!((RouterInfo)ds).isHidden()) { _state.addPending(peer); sendStore((RouterInfo)ds, peerTimeout); //} @@ -411,4 +415,4 @@ class StoreJob extends JobImpl { _state.complete(true); getContext().statManager().addRateData("netDb.storeFailedPeers", _state.getAttempted().size(), _state.getWhenCompleted()-_state.getWhenStarted()); } -} \ No newline at end of file +} diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java index ec7fc78b3..09bbe3ffa 100644 --- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java @@ -54,10 +54,12 @@ public class CreateRouterInfoJob extends JobImpl { info.setAddresses(getContext().commSystem().createAddresses()); Properties stats = getContext().statPublisher().publishStatistics(); stats.setProperty(RouterInfo.PROP_NETWORK_ID, Router.NETWORK_ID+""); - if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) - info.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL); getContext().router().addReachabilityCapability(info); info.setOptions(stats); + if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) + info.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL); + if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false"))) + info.addCapability(RouterInfo.CAPABILITY_HIDDEN); info.setPeers(new HashSet()); info.setPublished(getCurrentPublishDate(getContext())); RouterIdentity ident = new RouterIdentity(); diff --git a/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java b/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java index 0374922af..e10a237b5 100644 --- a/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java @@ -11,6 +11,8 @@ package net.i2p.router.startup; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.Collection; +import java.util.Set; import net.i2p.data.DataFormatException; import net.i2p.data.PrivateKey; @@ -76,6 +78,7 @@ public class LoadRouterInfoJob extends JobImpl { fis1 = new FileInputStream(rif); info = new RouterInfo(); info.readBytes(fis1); + getContext().router().updateExternalAddress(info.getAddresses(), false); _log.debug("Reading in routerInfo from " + rif.getAbsolutePath() + " and it has " + info.getAddresses().size() + " addresses"); } diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java index decdd9522..0ecd51249 100644 --- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java @@ -129,6 +129,11 @@ public class RebuildRouterInfoJob extends JobImpl { info.setOptions(stats); if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) info.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL); + + // Set caps=H for hidden mode routers + if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false"))) + info.addCapability(RouterInfo.CAPABILITY_HIDDEN); + getContext().router().addReachabilityCapability(info); // info.setPeers(new HashSet()); // this would have the trusted peers info.setPublished(CreateRouterInfoJob.getCurrentPublishDate(getContext())); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 2e551c4c7..723e32191 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -815,13 +815,29 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority boolean wantsRebuild = false; if ( (_externalAddress == null) || !(_externalAddress.equals(addr)) ) wantsRebuild = true; + RouterAddress oldAddress = _externalAddress; _externalAddress = addr; if (_log.shouldLog(Log.INFO)) _log.info("Address rebuilt: " + addr); - replaceAddress(addr); + replaceAddress(addr, oldAddress); if (allowRebuildRouterInfo) _context.router().rebuildRouterInfo(); } + + protected void replaceAddress(RouterAddress address, RouterAddress oldAddress) { + replaceAddress(address); + if (oldAddress != null) { + // fire a router.updateExternalAddress only if the address /really/ changed. + // updating the introducers doesn't require a real change, only updating the + // IP or port does. + UDPAddress old = new UDPAddress(oldAddress); + InetAddress oldHost = old.getHostAddress(); + UDPAddress newAddr = new UDPAddress(address); + InetAddress newHost = newAddr.getHostAddress(); + if ( (old.getPort() != newAddr.getPort()) || (!oldHost.equals(newHost)) ) + _context.router().updateExternalAddress(address, true); + } + } public boolean introducersRequired() { String forceIntroducers = _context.getProperty(PROP_FORCE_INTRODUCERS); diff --git a/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java b/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java index 88892f7bd..afa3dd7d0 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java @@ -16,6 +16,7 @@ import net.i2p.router.HandlerJobBuilder; import net.i2p.router.Job; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; +import net.i2p.router.Router; import net.i2p.router.message.GarlicMessageBuilder; import net.i2p.router.message.PayloadGarlicConfig; import net.i2p.router.message.SendMessageDirectJob; @@ -76,6 +77,11 @@ public class HandleTunnelCreateMessageJob extends JobImpl { public static final int MAX_DURATION_SECONDS = 15*60; private int shouldAccept() { + // Should not see any initiation requests in hidden mode + if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false"))) { + return TunnelHistory.TUNNEL_REJECT_CRIT; + } + if (_request.getDurationSeconds() >= MAX_DURATION_SECONDS) return TunnelHistory.TUNNEL_REJECT_CRIT; Hash nextRouter = _request.getNextRouter(); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java index 50dd485aa..980b1cc55 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -116,7 +116,12 @@ abstract class TunnelPeerSelector { * Pick peers that we want to avoid */ public Set getExclude(RouterContext ctx, boolean isInbound, boolean isExploratory) { - if (filterUnreachable(ctx, isInbound, isExploratory)) { + // we may want to update this to skip 'hidden' or 'unreachable' peers, but that + // isn't safe, since they may publish one set of routerInfo to us and another to + // other peers. the defaults for filterUnreachable has always been to return false, + // but might as well make it explicit with a "false &&" + + if (false && filterUnreachable(ctx, isInbound, isExploratory)) { List caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE); if (caps == null) return new HashSet(0); HashSet rv = new HashSet(caps);