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