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.
This commit is contained in:
jrandom
2005-11-26 09:16:11 +00:00
committed by zzz
parent ef82cc4f20
commit 9089fdd2d5
23 changed files with 319 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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) {

View File

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

View File

@ -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());
}

View File

@ -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);
}
/**

View File

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

View File

@ -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());
}
}
}

View File

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

View File

@ -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");
}

View File

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

View File

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

View File

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

View File

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