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);