diff --git a/history.txt b/history.txt index 6a7acc1aab13d84f5810e62ba9baa6e81134f0dd..5d3eda0d98a3d5ba907251ff649ea8129caab64a 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,15 @@ +2008-06-10 zzz + * Floodfill: Add new FloodfillMonitorJob, which tracks active + floodfills, and automatically enables/disables floodfill on + Class O routers to maintain 5-7 total active floodfills + * NetDb Stats: + - Remove several more stats + - Don't publish bw stats in first hour of uptime + - Publish floodfill stats even if other stats are disabled + - Changes not effective until 0.6.2.1 to provide cover. + * graphs.jsp: Fix a bug where it tries to display the combined + bandwidth graph when it isn't available + 2008-06-09 zzz * Propagate i2.i2p.i2p-0.6.2.1-pre branch to i2p.i2p diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 1edabb54b0f0d95c95ed97263a19d955883700ed..b3568c1fd8b5f70904588e6fd0a63ef1e4ea194c 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = "0.6.2"; - public final static long BUILD = 1; + public final static long BUILD = 2; 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/networkdb/kademlia/FloodfillMonitorJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java new file mode 100644 index 0000000000000000000000000000000000000000..cc31d1bcd3d5c6868279d625b21db7f4332c934b --- /dev/null +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java @@ -0,0 +1,158 @@ +package net.i2p.router.networkdb.kademlia; + +import java.util.List; +import java.util.Properties; + +import net.i2p.data.Hash; +import net.i2p.data.RouterAddress; +import net.i2p.data.RouterInfo; +import net.i2p.util.Log; +import net.i2p.router.JobImpl; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.peermanager.PeerProfile; + +/** + * Simple job to monitor the floodfill pool. + * If we are class O, and meet some other criteria, + * we will automatically become floodfill if there aren't enough. + * But only change ff status every few hours to minimize ff churn. + * + */ +class FloodfillMonitorJob extends JobImpl { + private Log _log; + private FloodfillNetworkDatabaseFacade _facade; + private long _lastChanged; + + private static final int REQUEUE_DELAY = 60*60*1000; + private static final long MIN_UPTIME = 2*60*60*1000; + private static final long MIN_CHANGE_DELAY = 6*60*60*1000; + private static final int MIN_FF = 5; + private static final int MAX_FF = 7; + private static final String PROP_FLOODFILL_PARTICIPANT = "router.floodfillParticipant"; + + public FloodfillMonitorJob(RouterContext context, FloodfillNetworkDatabaseFacade facade) { + super(context); + _facade = facade; + _log = context.logManager().getLog(FloodfillMonitorJob.class); + _lastChanged = 0; + } + + public String getName() { return "Monitor the floodfill pool"; } + public void runJob() { + boolean ff = shouldBeFloodfill(); + _facade.setFloodfillEnabled(ff); + if (_log.shouldLog(Log.INFO)) + _log.info("Should we be floodfill? " + ff); + requeue((REQUEUE_DELAY / 2) + getContext().random().nextInt(REQUEUE_DELAY)); + } + + private boolean shouldBeFloodfill() { + // Only if not shutting down... + if (getContext().getProperty(Router.PROP_SHUTDOWN_IN_PROGRESS) != null) + return false; + + String enabled = getContext().getProperty(PROP_FLOODFILL_PARTICIPANT, "auto"); + if ("true".equals(enabled)) + return true; + if ("false".equals(enabled)) + return false; + + // auto from here down + + // Only if up a while... + if (getContext().router().getUptime() < MIN_UPTIME) + return false; + + // Only if class O... + if (getContext().router().getRouterInfo().getCapabilities().indexOf("O") < 0) + return false; + + // This list may include ourselves... + List floodfillPeers = _facade.getFloodfillPeers(); + long now = getContext().clock().now(); + // We know none at all! Must be our turn... + if (floodfillPeers == null || floodfillPeers.size() <= 0) { + _lastChanged = now; + return true; + } + + // Only change status every so often + boolean wasFF = _facade.floodfillEnabled(); + if (_lastChanged + MIN_CHANGE_DELAY > now) + return wasFF; + + // This is similar to the qualification we do in FloodOnlySearchJob.runJob(). + // Count the "good" ff peers. + // + // Who's not good? + // the unheard-from, unprofiled, failing, unreachable and shitlisted ones. + // We should hear from floodfills pretty frequently so set a 60m time limit. + // If unprofiled we haven't talked to them in a long time. + // We aren't contacting the peer directly, so shitlist doesn't strictly matter, + // but it's a bad sign, and we often shitlist a peer before we fail it... + // + // Future: use Integration calculation + // + int ffcount = floodfillPeers.size(); + int failcount = 0; + long before = now - 60*60*1000; + for (int i = 0; i < floodfillPeers.size(); i++) { + Hash peer = (Hash)floodfillPeers.get(i); + if (peer.equals(getContext().routerHash())) + continue; + PeerProfile profile = getContext().profileOrganizer().getProfile(peer); + if (profile == null || profile.getLastHeardFrom() < before || + profile.getIsFailing() || getContext().shitlist().isShitlisted(peer) || + getContext().commSystem().wasUnreachable(peer)) + failcount++; + } + + int good = ffcount - failcount; + boolean happy = getContext().router().getRouterInfo().getCapabilities().indexOf("R") >= 0; + // Use the same job lag test as in RouterThrottleImpl + happy = happy && getContext().jobQueue().getMaxLag() < 2*1000; + // Only if we're pretty well integrated... + happy = happy && _facade.getKnownRouters() >= 200; + happy = happy && getContext().commSystem().countActivePeers() >= 50; + happy = happy && getContext().tunnelManager().getParticipatingCount() >= 100; + // We need an address and no introducers + if (happy) { + RouterAddress ra = getContext().router().getRouterInfo().getTargetAddress("SSU"); + if (ra == null) + happy = false; + else { + Properties props = ra.getOptions(); + if (props == null || props.getProperty("ihost0") != null) + happy = false; + } + } + + + // Too few, and we're reachable, let's volunteer + if (good < MIN_FF && happy) { + if (!wasFF) { + _lastChanged = now; + if (_log.shouldLog(Log.ERROR)) + _log.error("Only " + good + " ff peers and we want " + MIN_FF + " so we are becoming floodfill"); + } + return true; + } + + // Too many, or we aren't reachable, let's stop + if (good > MAX_FF || (good > MIN_FF && !happy)) { + if (wasFF) { + _lastChanged = now; + if (_log.shouldLog(Log.ERROR)) + _log.error("Have " + good + " ff peers and we need only " + MIN_FF + " to " + MAX_FF + + " so we are disabling floodfill; reachable? " + happy); + } + return false; + } + + if (_log.shouldLog(Log.INFO)) + _log.info("Have " + good + " ff peers, not changing, enabled? " + wasFF + "; reachable? " + happy); + return wasFF; + } + +} 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 a2d1f01cfeab1edd7cc4bbe6c20d5456f414897e..0d0cc47060cebbc81b6dcce7c0832c28d22d2c2a 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -12,13 +12,13 @@ import net.i2p.util.Log; */ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacade { public static final char CAPACITY_FLOODFILL = 'f'; - private static final String PROP_FLOODFILL_PARTICIPANT = "router.floodfillParticipant"; - private static final String DEFAULT_FLOODFILL_PARTICIPANT = "false"; private Map _activeFloodQueries; + private boolean _floodfillEnabled; public FloodfillNetworkDatabaseFacade(RouterContext context) { super(context); _activeFloodQueries = new HashMap(); + _floodfillEnabled = false; _context.statManager().createRateStat("netDb.successTime", "How long a successful search takes", "NetworkDatabase", new long[] { 60*60*1000l, 24*60*60*1000l }); _context.statManager().createRateStat("netDb.failedTime", "How long a failed search takes", "NetworkDatabase", new long[] { 60*60*1000l, 24*60*60*1000l }); @@ -33,6 +33,11 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad _context.statManager().createRateStat("netDb.republishQuantity", "How many peers do we need to send a found leaseSet to?", "NetworkDatabase", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); } + public void startup() { + super.startup(); + _context.jobQueue().addJob(new FloodfillMonitorJob(_context, this)); + } + protected void createHandlers() { _context.inNetMessagePool().registerHandlerJobBuilder(DatabaseLookupMessage.MESSAGE_TYPE, new FloodfillDatabaseLookupMessageHandler(_context)); _context.inNetMessagePool().registerHandlerJobBuilder(DatabaseStoreMessage.MESSAGE_TYPE, new FloodfillDatabaseStoreMessageHandler(_context, this)); @@ -106,10 +111,10 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad protected PeerSelector createPeerSelector() { return new FloodfillPeerSelector(_context); } - public boolean floodfillEnabled() { return floodfillEnabled(_context); } + public void setFloodfillEnabled(boolean yes) { _floodfillEnabled = yes; } + public boolean floodfillEnabled() { return _floodfillEnabled; } public static boolean floodfillEnabled(RouterContext ctx) { - String enabled = ctx.getProperty(PROP_FLOODFILL_PARTICIPANT, DEFAULT_FLOODFILL_PARTICIPANT); - return "true".equals(enabled); + return ((FloodfillNetworkDatabaseFacade)ctx.netDb()).floodfillEnabled(); } public static boolean isFloodfill(RouterInfo peer) {