diff --git a/apps/i2psnark/java/build.xml b/apps/i2psnark/java/build.xml
index 517c40fae87aa69bcc7acdbd28a3c8157bb02b25..ed03f847f85aa33a29ad5839119f442096d4d07c 100644
--- a/apps/i2psnark/java/build.xml
+++ b/apps/i2psnark/java/build.xml
@@ -14,7 +14,7 @@
             srcdir="./src" 
             debug="true" deprecation="on" source="1.3" target="1.3" 
             destdir="./build/obj" 
-            classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
+            classpath="../../../core/java/build/i2p.jar:../../../router/java/build/router.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
     </target>
     <target name="jar" depends="builddep, compile">
         <jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.class">
diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index 6143c9877f0bce3fba5e7d8697f9b02817b35345..d71daf5643d76b07cd823817f5f2bb57be99afbb 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -34,6 +34,7 @@ public class I2PSnarkUtil {
     private boolean _configured;
     private Set _shitlist;
     private int _maxUploaders;
+    private int _maxUpBW;
     
     private I2PSnarkUtil() {
         _context = I2PAppContext.getGlobalContext();
@@ -79,6 +80,11 @@ public class I2PSnarkUtil {
         _configured = true;
     }
     
+    public void setMaxUpBW(int limit) {
+        _maxUpBW = limit;
+        _configured = true;
+    }
+    
     public String getI2CPHost() { return _i2cpHost; }
     public int getI2CPPort() { return _i2cpPort; }
     public Map getI2CPOptions() { return _opts; }
@@ -86,6 +92,7 @@ public class I2PSnarkUtil {
     public int getEepProxyPort() { return _proxyPort; }
     public boolean getEepProxySet() { return _shouldProxy; }
     public int getMaxUploaders() { return _maxUploaders; }
+    public int getMaxUpBW() { return _maxUpBW; }
     
     /**
      * Connect to the router, if we aren't already
@@ -197,6 +204,8 @@ public class I2PSnarkUtil {
     }
     
     String getOurIPString() {
+        if (_manager == null)
+            return "unknown";
         I2PSession sess = _manager.getSession();
         if (sess != null) {
             Destination dest = sess.getMyDestination();
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
index 7a3294f1469eb75b9eaaa5f26026d1fcfe8dc088..57edb55779fbafa63e1cdfb6d61aba1bb38da381 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
@@ -68,6 +68,7 @@ class PeerCheckerTask extends TimerTask
         // we will add them back to the end of the list.
         List removed = new ArrayList();
         int uploadLimit = coordinator.allowedUploaders();
+        boolean overBWLimit = coordinator.overUpBWLimit();
         while (it.hasNext())
           {
             Peer peer = (Peer)it.next();
@@ -109,7 +110,8 @@ class PeerCheckerTask extends TimerTask
             // (Note use of coordinator.uploaders)
             if (((coordinator.uploaders == uploadLimit
                 && coordinator.interestedAndChoking > 0)
-                || coordinator.uploaders > uploadLimit)
+                || coordinator.uploaders > uploadLimit
+                || overBWLimit)
                 && !peer.isChoking())
               {
                 // Check if it still wants pieces from us.
@@ -125,6 +127,15 @@ class PeerCheckerTask extends TimerTask
                     it.remove();
                     removed.add(peer);
                   }
+                else if (overBWLimit)
+                  {
+                    Snark.debug("BW limit, choke peer: " + peer,
+                                Snark.INFO);
+                    peer.setChoking(true);
+                    uploaders--;
+                    coordinator.uploaders--;
+                    removedCount++;
+                  }
                 else if (peer.isInteresting() && peer.isChoked())
                   {
                     // If they are choking us make someone else a downloader
@@ -209,7 +220,8 @@ class PeerCheckerTask extends TimerTask
           }
         
         // Optimistically unchoke a peer
-        coordinator.unchokePeer();
+        if (!overBWLimit)
+            coordinator.unchokePeer();
 
         // Put peers back at the end of the list that we removed earlier.
         coordinator.peers.addAll(removed);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
index 6b5780b101bed7e6f1f60420c25f037a639221de..ae74bfd6071ae339042f41f0feb4d4367c2857ad 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
@@ -52,8 +52,8 @@ public class PeerCoordinator implements PeerListener
   private long uploaded;
   private long downloaded;
   final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
-  private long uploaded_old[] = {0,0,0,0,0,0};
-  private long downloaded_old[] = {0,0,0,0,0,0};
+  private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
+  private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
 
   // synchronize on this when changing peers or downloaders
   final List peers = new ArrayList();
@@ -195,11 +195,17 @@ public class PeerCoordinator implements PeerListener
   private long getRate(long array[])
   {
     long rate = 0;
+    int i = 0;
     synchronized(array) {
-      for (int i = 0; i < RATE_DEPTH; i++)
+      for ( ; i < RATE_DEPTH; i++) {
+        if (array[i] < 0)
+            break;
         rate += array[i];
+      }
     }
-    return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
+    if (i == 0)
+        return 0;
+    return rate / (i * CHECK_PERIOD / 1000);
   }
 
   public MetaInfo getMetaInfo()
@@ -819,5 +825,10 @@ public class PeerCoordinator implements PeerListener
     else
         return MAX_UPLOADERS;
   }
+
+  public boolean overUpBWLimit()
+  {
+    return Snark.overUpBWLimit();
+  }
 }
 
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
index 71bb49f53f49591b1edeac8ee7ac063654bfd785..07d2dbd875ecd6a0f8747ec443871f9362fa1103 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
@@ -776,4 +776,18 @@ public class Snark
     // Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
     return totalUploaders > limit;
   }
+
+  public static boolean overUpBWLimit() {
+    PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
+    if (coordinators == null)
+      return false;
+    long total = 0;
+    for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
+      PeerCoordinator c = (PeerCoordinator)iter.next();
+      if (!c.halted())
+        total += c.getUploadRate();
+    }
+    long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
+    return total > limit;
+  }
 }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index b93fa5a1e9751b79d1b9ffcdd69436a0bb90ac06..7275a83b74dbb06f1707e15177ea420a8a278794 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -5,6 +5,7 @@ import java.util.*;
 import net.i2p.I2PAppContext;
 import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
+import net.i2p.router.RouterContext;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
 
@@ -30,6 +31,7 @@ public class SnarkManager implements Snark.CompleteListener {
     public static final String PROP_EEP_HOST = "i2psnark.eepHost";
     public static final String PROP_EEP_PORT = "i2psnark.eepPort";
     public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
+    public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
     public static final String PROP_DIR = "i2psnark.dir";
     public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
     public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
@@ -41,6 +43,9 @@ public class SnarkManager implements Snark.CompleteListener {
     public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
     public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
     
+    public static final int MIN_UP_BW = 2;
+    public static final int DEFAULT_MAX_UP_BW = 10;
+
     private SnarkManager() {
         _snarks = new HashMap();
         _addSnarkLock = new Object();
@@ -112,6 +117,12 @@ public class SnarkManager implements Snark.CompleteListener {
             _config.setProperty(PROP_EEP_PORT, "4444");
         if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
             _config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
+        if (!_config.containsKey(PROP_UPBW_MAX)) {
+            if (_context instanceof RouterContext)
+                _config.setProperty(PROP_UPBW_MAX, "" + (((RouterContext)_context).bandwidthLimiter().getOutboundKBytesPerSecond() / 3));
+            else
+                _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
+        }
         if (!_config.containsKey(PROP_DIR))
             _config.setProperty(PROP_DIR, "i2psnark");
         if (!_config.containsKey(PROP_AUTO_START))
@@ -143,6 +154,7 @@ public class SnarkManager implements Snark.CompleteListener {
         if (eepHost != null)
             I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
         I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
+        I2PSnarkUtil.instance().setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
         getDataDir().mkdirs();
     }
     
@@ -159,7 +171,7 @@ public class SnarkManager implements Snark.CompleteListener {
     
     public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost, 
                              String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
-                             String upLimit, boolean useOpenTrackers, String openTrackers) {
+                             String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) {
         boolean changed = false;
         if (eepHost != null) {
             int port = I2PSnarkUtil.instance().getEepProxyPort();
@@ -188,6 +200,20 @@ public class SnarkManager implements Snark.CompleteListener {
                 }
             }
         }
+        if (upBW != null) {
+            int limit = I2PSnarkUtil.instance().getMaxUpBW();
+            try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {}
+            if ( limit != I2PSnarkUtil.instance().getMaxUpBW()) {
+                if ( limit >= MIN_UP_BW ) {
+                    I2PSnarkUtil.instance().setMaxUpBW(limit);
+                    changed = true;
+                    _config.setProperty(PROP_UPBW_MAX, "" + limit);
+                    addMessage("Up BW limit changed to " + limit + "KBps");
+                } else {
+                    addMessage("Minimum Up BW limit is " + MIN_UP_BW + "KBps");
+                }
+            }
+        }
         if (i2cpHost != null) {
             int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
             String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index a381324bda86b5335721ea0ee20e1cdc9edab965..484edf0854f817dd381c09515d32169c71db1c53 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -293,9 +293,10 @@ public class I2PSnarkServlet extends HttpServlet {
             String i2cpPort = req.getParameter("i2cpPort");
             String i2cpOpts = req.getParameter("i2cpOpts");
             String upLimit = req.getParameter("upLimit");
+            String upBW = req.getParameter("upBW");
             boolean useOpenTrackers = req.getParameter("useOpenTrackers") != null;
             String openTrackers = req.getParameter("openTrackers");
-            _manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts, upLimit, useOpenTrackers, openTrackers);
+            _manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts, upLimit, upBW, useOpenTrackers, openTrackers);
         } else if ("Create torrent".equals(action)) {
             String baseData = req.getParameter("baseFile");
             if (baseData != null) {
@@ -700,7 +701,9 @@ public class I2PSnarkServlet extends HttpServlet {
         out.write("</select><br />\n");
 */
         out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
-                  + I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" /> peers<br />\n");
+                  + I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
+        out.write("Up bandwidth limit: <input type=\"text\" name=\"upBW\" value=\""
+                  + I2PSnarkUtil.instance().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 3 recommended)</i><br />\n");
         
         out.write("Use open trackers also: <input type=\"checkbox\" name=\"useOpenTrackers\" value=\"true\" " 
                   + (useOpenTrackers ? "checked " : "") 
@@ -712,11 +715,11 @@ public class I2PSnarkServlet extends HttpServlet {
         out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
                   + I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
         out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
-                  + I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" /><br />\n");
+                  + I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
         out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\"" 
                   + I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
         out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
-                  + I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" /> <br />\n");
+                  + I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
         StringBuffer opts = new StringBuffer(64);
         Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
         for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
@@ -850,7 +853,8 @@ class FetchAndAdd implements Runnable {
     }
     public void run() {
         _url = _url.trim();
-        File file = I2PSnarkUtil.instance().get(_url, false);
+        // 3 retries
+        File file = I2PSnarkUtil.instance().get(_url, false, 3);
         try {
             if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
                 _manager.addMessage("Torrent fetched from " + _url);