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:
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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()));
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user