diff --git a/history.txt b/history.txt
index 59aa2af21efe78fe578cff015e104465c4eaba3a..a3964357547f3cf693868dc072fb24d7142f0205 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,10 @@
+2012-11-16 zzz
+ * i2psnark: Fix rare IOOBE (ticket #777)
+ * NetDB:
+   - Implement automatic reseeding (ticket #521)
+   - Increase minimum routers
+ * Tunnels: Fix outbound tunnel message priority (ticket #719)
+
 2012-11-13 zzz
  * Bandwidth Limiter: Fix stats broken in -1
  * HTTP Proxy: Store referrer of new addresses in address book
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index c10128fe6223a59f4fa2320e38c3309487438a87..fecba78d6973d87e9930d631de787e05fec2a510 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 = 6;
+    public final static long BUILD = 7;
 
     /** for example "-test" */
     public final static String EXTRA = "";
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 633dbeff1a1a3111530a4f39e40b6f8c421ae0c9..9dcd9231805e78b377be05c3c21945752b2dcda3 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
@@ -105,10 +105,17 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
     public final static String PROP_DB_DIR = "router.networkDatabase.dbDir";
     public final static String DEFAULT_DB_DIR = "netDb";
     
+    /** Reseed if below this.
+     *  @since 0.9.4
+     */
+    static final int MIN_RESEED = ReseedChecker.MINIMUM;
+
     /** if we have less than this many routers left, don't drop any more,
      *  even if they're failing or doing bad stuff.
+     *  As of 0.9.4, we make this LOWER than the min for reseeding, so
+     *  a reseed will be forced if necessary.
      */
-    protected final static int MIN_REMAINING_ROUTERS = 25;
+    protected final static int MIN_REMAINING_ROUTERS = MIN_RESEED - 10;
     
     /** 
      * limits for accepting a dbDtore of a router (unless we dont 
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
index dc44e1345f78992b368dbfc7f8e7d3e7ef481bb2..e59ad6b31aa0e23cb9e7ea5c58bb02ee05df40a7 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
@@ -46,9 +46,9 @@ class PersistentDataStore extends TransientDataStore {
     private final KademliaNetworkDatabaseFacade _facade;
     private final Writer _writer;
     private final ReadJob _readJob;
-    private boolean _initialized;
+    private volatile boolean _initialized;
     
-    private final static int READ_DELAY = 60*1000;
+    private final static int READ_DELAY = 2*60*1000;
     
     /**
      *  @param dbDir relative path
@@ -319,14 +319,21 @@ class PersistentDataStore extends TransientDataStore {
         return data.getDate();
     }
     
-    /** This is only for manual reseeding? Why bother every 60 sec??? */
+    /**
+     *  This is mostly for manual reseeding, i.e. the user manually
+     *  copies RI files to the directory. Nobody does this,
+     *  so this is run way too often.
+     *  Reseed task calls wakeup() on completion.
+     *  As of 0.9.4, also initiates an automatic reseed if necessary.
+     */
     private class ReadJob extends JobImpl {
-        private boolean _alreadyWarned;
         private long _lastModified;
+        private long _lastReseed;
+        private static final int MIN_ROUTERS = KademliaNetworkDatabaseFacade.MIN_RESEED;
+        private static final long MIN_RESEED_INTERVAL = 90*60*1000;
 
         public ReadJob() {
             super(PersistentDataStore.this._context);
-            _alreadyWarned = false;
         }
 
         public String getName() { return "DB Read Job"; }
@@ -334,7 +341,8 @@ class PersistentDataStore extends TransientDataStore {
         public void runJob() {
             // check directory mod time to save a lot of object churn in scanning all the file names
             long lastMod = _dbDir.lastModified();
-            if (lastMod > _lastModified) {
+            // if size() (= RI + LS) is too low, call anyway to check for reseed
+            if (lastMod > _lastModified || size() < MIN_ROUTERS + 10) {
                 _lastModified = lastMod;
                 _log.info("Rereading new files");
                 // synch with the writer job
@@ -354,9 +362,7 @@ class PersistentDataStore extends TransientDataStore {
 
                 File routerInfoFiles[] = _dbDir.listFiles(RouterInfoFilter.getInstance());
                 if (routerInfoFiles != null) {
-                    routerCount += routerInfoFiles.length;
-                    if (routerInfoFiles.length > 5)
-                        _alreadyWarned = false;
+                    routerCount = routerInfoFiles.length;
                     for (int i = 0; i < routerInfoFiles.length; i++) {
                         // drop out if the router gets killed right after startup
                         if (!_context.router().isAlive())
@@ -373,10 +379,16 @@ class PersistentDataStore extends TransientDataStore {
                     }
                 }
             
-            if (!_alreadyWarned) {
-                _facade.reseedChecker().checkReseed(routerCount);
-                _alreadyWarned = true;
+            if (!_initialized) {
+                if (_facade.reseedChecker().checkReseed(routerCount))
+                    _lastReseed = _context.clock().now();
                 _initialized = true;
+            } else if (_lastReseed < _context.clock().now() - MIN_RESEED_INTERVAL) {
+                int count = Math.min(routerCount, size());
+                if (count < MIN_ROUTERS) {
+                    if (_facade.reseedChecker().checkReseed(count))
+                        _lastReseed = _context.clock().now();
+                }
             }
         }
     }
diff --git a/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java b/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java
index efb9b33e2120fdf1d9b91d9a783d6ede1f76c0da..2023452a8bd3ab677579a0e8b0dbf538a1976585 100644
--- a/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java
+++ b/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java
@@ -4,6 +4,7 @@ import java.io.File;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import net.i2p.router.RouterContext;
+import net.i2p.util.Addresses;
 import net.i2p.util.Log;
 
 /**
@@ -23,10 +24,10 @@ public class ReseedChecker {
     private final RouterContext _context;
     private final Log _log;
     private final AtomicBoolean _inProgress = new AtomicBoolean();
-    private String _lastStatus = "";
-    private String _lastError = "";
+    private volatile String _lastStatus = "";
+    private volatile String _lastError = "";
 
-    private static final int MINIMUM = 15;
+    public static final int MINIMUM = 50;
 
     /**
      *  All reseeding must be done through this instance.
@@ -64,6 +65,10 @@ public class ReseedChecker {
         File noReseedFileAlt2 = new File(_context.getConfigDir(), ".i2pnoreseed");
         File noReseedFileAlt3 = new File(_context.getConfigDir(), "noreseed.i2p");
         if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
+            if (!Addresses.isConnected()) {
+                _log.logAlways(Log.WARN, "Cannot reseed, no network connection");
+                return false;
+            }
             if (count <= 1)
                 _log.logAlways(Log.INFO, "Downloading peer router information for a new I2P installation");
             else
@@ -96,7 +101,7 @@ public class ReseedChecker {
             }
         } else {
             if (_log.shouldLog(Log.WARN))
-                _log.warn("Reseed already in prgress");
+                _log.warn("Reseed already in progress");
             return false;
         }
     }