diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index 7ad6d83310..d892469a3c 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -195,6 +195,8 @@ public class NetDbRenderer { buf.append("

Published (RAP) Leasesets: ").append(netdb.getKnownLeaseSets()); buf.append("

Mod Data: \"").append(DataHelper.getUTF8(_context.routingKeyGenerator().getModData())) .append("\" Last Changed: ").append(new Date(_context.routingKeyGenerator().getLastChanged())); + buf.append("

Next Mod Data: \"").append(DataHelper.getUTF8(_context.routingKeyGenerator().getNextModData())) + .append("\" Change in: ").append(DataHelper.formatDuration(_context.routingKeyGenerator().getTimeTillMidnight())); int ff = _context.peerManager().getPeersByCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL).size(); buf.append("

Known Floodfills: ").append(ff); buf.append("

Currently Floodfill? "); diff --git a/core/java/src/net/i2p/data/DatabaseEntry.java b/core/java/src/net/i2p/data/DatabaseEntry.java index 92e9d97068..e10e4abf0d 100644 --- a/core/java/src/net/i2p/data/DatabaseEntry.java +++ b/core/java/src/net/i2p/data/DatabaseEntry.java @@ -107,15 +107,17 @@ public abstract class DatabaseEntry extends DataStructureImpl { */ public Hash getRoutingKey() { RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance(); - if ((gen.getModData() == null) || (_routingKeyGenMod == null) - || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod))) { + byte[] mod = gen.getModData(); + if (!DataHelper.eq(mod, _routingKeyGenMod)) { _currentRoutingKey = gen.getRoutingKey(getHash()); - _routingKeyGenMod = gen.getModData(); + _routingKeyGenMod = mod; } return _currentRoutingKey; } - + /** + * @deprecated unused + */ public void setRoutingKey(Hash key) { _currentRoutingKey = key; } diff --git a/core/java/src/net/i2p/data/RoutingKeyGenerator.java b/core/java/src/net/i2p/data/RoutingKeyGenerator.java index 08e8ee66d5..e66049cc78 100644 --- a/core/java/src/net/i2p/data/RoutingKeyGenerator.java +++ b/core/java/src/net/i2p/data/RoutingKeyGenerator.java @@ -17,6 +17,7 @@ import java.util.TimeZone; import net.i2p.I2PAppContext; import net.i2p.crypto.SHA256Generator; +import net.i2p.util.HexDump; import net.i2p.util.Log; /** @@ -37,6 +38,8 @@ import net.i2p.util.Log; * Also - the method generateDateBasedModData() should be called after midnight GMT * once per day to generate the correct routing keys! * + * Warning - API subject to change. Not for use outside the router. + * */ public class RoutingKeyGenerator { private final Log _log; @@ -54,6 +57,8 @@ public class RoutingKeyGenerator { } private volatile byte _currentModData[]; + private volatile byte _nextModData[]; + private volatile long _nextMidnight; private volatile long _lastChanged; private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); @@ -61,14 +66,74 @@ public class RoutingKeyGenerator { private static final int LENGTH = FORMAT.length(); private final static SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT); + /** + * The current (today's) mod data. + * Warning - not a copy, do not corrupt. + * + * @return non-null, 8 bytes + */ public byte[] getModData() { return _currentModData; } + /** + * Tomorrow's mod data. + * Warning - not a copy, do not corrupt. + * For debugging use only. + * + * @return non-null, 8 bytes + * @since 0.9.10 + */ + public byte[] getNextModData() { + return _nextModData; + } + public long getLastChanged() { return _lastChanged; } + /** + * How long until midnight (ms) + * + * @return could be slightly negative + * @since 0.9.10 moved from UpdateRoutingKeyModifierJob + */ + public long getTimeTillMidnight() { + return _nextMidnight - _context.clock().now(); + } + + /** + * Set _cal to midnight for the time given. + * Caller must synch. + * @since 0.9.10 + */ + private void setCalToPreviousMidnight(long now) { + _cal.setTime(new Date(now)); + _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround + _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround + _cal.set(Calendar.HOUR_OF_DAY, 0); + _cal.set(Calendar.MINUTE, 0); + _cal.set(Calendar.SECOND, 0); + _cal.set(Calendar.MILLISECOND, 0); + } + + /** + * Generate mod data from _cal. + * Caller must synch. + * @since 0.9.10 + */ + private byte[] generateModDataFromCal() { + Date today = _cal.getTime(); + + String modVal = _fmt.format(today); + if (modVal.length() != LENGTH) + throw new IllegalStateException(); + byte[] mod = new byte[LENGTH]; + for (int i = 0; i < LENGTH; i++) + mod[i] = (byte)(modVal.charAt(i) & 0xFF); + return mod; + } + /** * Update the current modifier data with some bytes derived from the current * date (yyyyMMdd in GMT) @@ -77,34 +142,26 @@ public class RoutingKeyGenerator { */ public synchronized boolean generateDateBasedModData() { long now = _context.clock().now(); - _cal.setTime(new Date(now)); - _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround - _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround - _cal.set(Calendar.HOUR_OF_DAY, 0); - _cal.set(Calendar.MINUTE, 0); - _cal.set(Calendar.SECOND, 0); - _cal.set(Calendar.MILLISECOND, 0); - Date today = _cal.getTime(); - - String modVal = _fmt.format(today); - if (modVal.length() != LENGTH) - throw new IllegalStateException(); - byte[] mod = new byte[LENGTH]; - for (int i = 0; i < LENGTH; i++) - mod[i] = (byte)(modVal.charAt(i) & 0xFF); + setCalToPreviousMidnight(now); + byte[] mod = generateModDataFromCal(); boolean changed = !DataHelper.eq(_currentModData, mod); if (changed) { + // add a day and store next midnight and mod data for convenience + _cal.add(Calendar.DATE, 1); + _nextMidnight = _cal.getTime().getTime(); + byte[] next = generateModDataFromCal(); _currentModData = mod; + _nextModData = next; _lastChanged = now; if (_log.shouldLog(Log.INFO)) - _log.info("Routing modifier generated: " + modVal); + _log.info("Routing modifier generated: " + HexDump.dump(mod)); } return changed; } /** * Generate a modified (yet consistent) hash from the origKey by generating the - * SHA256 of the targetKey with the current modData appended to it, *then* + * SHA256 of the targetKey with the current modData appended to it * * This makes Sybil's job a lot harder, as she needs to essentially take over the * whole keyspace. @@ -112,10 +169,29 @@ public class RoutingKeyGenerator { * @throws IllegalArgumentException if origKey is null */ public Hash getRoutingKey(Hash origKey) { + return getKey(origKey, _currentModData); + } + + /** + * Get the routing key using tomorrow's modData, not today's + * + * @since 0.9.10 + */ + public Hash getNextRoutingKey(Hash origKey) { + return getKey(origKey, _nextModData); + } + + /** + * Generate a modified (yet consistent) hash from the origKey by generating the + * SHA256 of the targetKey with the specified modData appended to it + * + * @throws IllegalArgumentException if origKey is null + */ + private static Hash getKey(Hash origKey, byte[] modData) { if (origKey == null) throw new IllegalArgumentException("Original key is null"); byte modVal[] = new byte[Hash.HASH_LENGTH + LENGTH]; System.arraycopy(origKey.getData(), 0, modVal, 0, Hash.HASH_LENGTH); - System.arraycopy(_currentModData, 0, modVal, Hash.HASH_LENGTH, LENGTH); + System.arraycopy(modData, 0, modVal, Hash.HASH_LENGTH, LENGTH); return SHA256Generator.getInstance().calculateHash(modVal); } diff --git a/history.txt b/history.txt index c44a8a1f1e..43a01108f2 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,10 @@ +2013-12-14 zzz +* NetDB: + - Just before midnight, flood to new location too so lookups + don't fail after keyspace rotation (ticket #510) + - Refactor RoutingKeyGenerator and UpdateRoutingKeyModifierJob + in support of the above + 2013-12-13 zzz * i2ptunnel: Show destination for persistent client key only if available; show b32 for the key as well diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 282c18b422..0725033fa6 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 2; + public final static long BUILD = 3; /** for example "-test" */ public final static String EXTRA = ""; 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 e8cae585a6..0da4cf05e0 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -43,6 +43,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad private static final int FLOOD_PRIORITY = OutNetMessage.PRIORITY_NETDB_FLOOD; private static final int FLOOD_TIMEOUT = 30*1000; + private static final long NEXT_RKEY_RI_ADVANCE_TIME = 45*60*1000; + private static final long NEXT_RKEY_LS_ADVANCE_TIME = 10*60*1000; + private static final int NEXT_FLOOD_QTY = 2; public FloodfillNetworkDatabaseFacade(RouterContext context) { super(context); @@ -197,6 +200,23 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad Hash rkey = _context.routingKeyGenerator().getRoutingKey(key); FloodfillPeerSelector sel = (FloodfillPeerSelector)getPeerSelector(); List peers = sel.selectFloodfillParticipants(rkey, MAX_TO_FLOOD, getKBuckets()); + long until = _context.routingKeyGenerator().getTimeTillMidnight(); + if (until < NEXT_RKEY_LS_ADVANCE_TIME || + (ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO && until < NEXT_RKEY_RI_ADVANCE_TIME)) { + // to avoid lookup failures after midnight, also flood to some closest to the + // next routing key for a period of time before midnight. + Hash nkey = _context.routingKeyGenerator().getNextRoutingKey(key); + List nextPeers = sel.selectFloodfillParticipants(nkey, NEXT_FLOOD_QTY, getKBuckets()); + int i = 0; + for (Hash h : nextPeers) { + if (!peers.contains(h)) { + peers.add(h); + i++; + } + } + if (i > 0 && _log.shouldLog(Log.INFO)) + _log.info("Flooding the entry for " + key + " to " + i + " more, just before midnight"); + } int flooded = 0; for (int i = 0; i < peers.size(); i++) { Hash peer = peers.get(i); diff --git a/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java b/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java index ae469152e6..2952c2eda4 100644 --- a/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java +++ b/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java @@ -8,11 +8,7 @@ package net.i2p.router.tasks; * */ -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - +import net.i2p.data.RoutingKeyGenerator; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -26,7 +22,6 @@ import net.i2p.util.Log; */ public class UpdateRoutingKeyModifierJob extends JobImpl { private final Log _log; - private final Calendar _cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); // Run every 15 minutes in case of time zone change, clock skew, etc. private static final long MAX_DELAY_FAILSAFE = 15*60*1000; @@ -38,29 +33,11 @@ public class UpdateRoutingKeyModifierJob extends JobImpl { public String getName() { return "Update Routing Key Modifier"; } public void runJob() { + RoutingKeyGenerator gen = getContext().routingKeyGenerator(); // make sure we requeue quickly if just before midnight - long delay = Math.min(MAX_DELAY_FAILSAFE, getTimeTillMidnight()); + long delay = Math.max(5, Math.min(MAX_DELAY_FAILSAFE, gen.getTimeTillMidnight())); // TODO tell netdb if mod data changed? - getContext().routingKeyGenerator().generateDateBasedModData(); + gen.generateDateBasedModData(); requeue(delay); } - - private long getTimeTillMidnight() { - long now = getContext().clock().now(); - _cal.setTime(new Date(now)); - _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround - _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround - _cal.add(Calendar.DATE, 1); - _cal.set(Calendar.HOUR_OF_DAY, 0); - _cal.set(Calendar.MINUTE, 0); - _cal.set(Calendar.SECOND, 0); - _cal.set(Calendar.MILLISECOND, 0); - long then = _cal.getTime().getTime(); - long howLong = then - now; - if (howLong < 0) // hi kaffe - howLong = 24*60*60*1000l + howLong; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Time till midnight: " + howLong + "ms"); - return howLong; - } }