forked from I2P_Developers/i2p.i2p
* NetDB: Add support for reseeding with su3 files (ticket #934)
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
2014-06-28 zzz
|
2014-06-28 zzz
|
||||||
|
* NetDB: Add support for reseeding with su3 files (ticket #934)
|
||||||
* SAM:
|
* SAM:
|
||||||
- Support SIGNATURE_TYPE, bump to 3.1 (ticket #1318)
|
- Support SIGNATURE_TYPE, bump to 3.1 (ticket #1318)
|
||||||
- Private key checking fixes (ticket #1318)
|
- Private key checking fixes (ticket #1318)
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 10;
|
public final static long BUILD = 11;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
@ -7,6 +7,7 @@ import java.io.IOException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -17,6 +18,7 @@ import java.util.Set;
|
|||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.crypto.SU3File;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.data.Hash;
|
import net.i2p.data.Hash;
|
||||||
@ -25,6 +27,7 @@ import net.i2p.router.RouterContext;
|
|||||||
import net.i2p.router.util.EventLog;
|
import net.i2p.router.util.EventLog;
|
||||||
import net.i2p.router.util.RFC822Date;
|
import net.i2p.router.util.RFC822Date;
|
||||||
import net.i2p.util.EepGet;
|
import net.i2p.util.EepGet;
|
||||||
|
import net.i2p.util.FileUtil;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SecureDirectory;
|
import net.i2p.util.SecureDirectory;
|
||||||
@ -49,8 +52,12 @@ public class Reseeder {
|
|||||||
|
|
||||||
// Reject unreasonably big files, because we download into a ByteArrayOutputStream.
|
// Reject unreasonably big files, because we download into a ByteArrayOutputStream.
|
||||||
private static final long MAX_RESEED_RESPONSE_SIZE = 2 * 1024 * 1024;
|
private static final long MAX_RESEED_RESPONSE_SIZE = 2 * 1024 * 1024;
|
||||||
|
private static final long MAX_SU3_RESPONSE_SIZE = 1024 * 1024;
|
||||||
/** limit to spend on a single host, to avoid getting stuck on one that is seriously overloaded */
|
/** limit to spend on a single host, to avoid getting stuck on one that is seriously overloaded */
|
||||||
private static final int MAX_TIME_PER_HOST = 7 * 60 * 1000;
|
private static final int MAX_TIME_PER_HOST = 7 * 60 * 1000;
|
||||||
|
private static final long MAX_FILE_AGE = 30*24*60*60*1000L;
|
||||||
|
/** change to false if hosts not ready at release */
|
||||||
|
private static final boolean ENABLE_SU3 = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE - URLs that are in both the standard and SSL groups must use the same hostname and path,
|
* NOTE - URLs that are in both the standard and SSL groups must use the same hostname and path,
|
||||||
@ -95,6 +102,8 @@ public class Reseeder {
|
|||||||
// Temp disabled since h2ik have been AWOL since 06-03-2013
|
// Temp disabled since h2ik have been AWOL since 06-03-2013
|
||||||
//"https://i2p.feared.eu/";
|
//"https://i2p.feared.eu/";
|
||||||
|
|
||||||
|
private static final String SU3_FILENAME = "i2pseeds.su3";
|
||||||
|
|
||||||
public static final String PROP_PROXY_HOST = "router.reseedProxyHost";
|
public static final String PROP_PROXY_HOST = "router.reseedProxyHost";
|
||||||
public static final String PROP_PROXY_PORT = "router.reseedProxyPort";
|
public static final String PROP_PROXY_PORT = "router.reseedProxyPort";
|
||||||
/** @since 0.8.2 */
|
/** @since 0.8.2 */
|
||||||
@ -284,7 +293,11 @@ public class Reseeder {
|
|||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < URLList.size() && _isRunning; i++) {
|
for (int i = 0; i < URLList.size() && _isRunning; i++) {
|
||||||
String url = URLList.get(i);
|
String url = URLList.get(i);
|
||||||
int dl = reseedOne(url, echoStatus);
|
int dl = 0;
|
||||||
|
if (ENABLE_SU3)
|
||||||
|
dl = reseedSU3(url + SU3_FILENAME, echoStatus);
|
||||||
|
if (dl <= 0)
|
||||||
|
dl = reseedOne(url, echoStatus);
|
||||||
if (dl > 0) {
|
if (dl > 0) {
|
||||||
total += dl;
|
total += dl;
|
||||||
// Don't go on to the next URL if we have enough
|
// Don't go on to the next URL if we have enough
|
||||||
@ -321,6 +334,7 @@ public class Reseeder {
|
|||||||
*
|
*
|
||||||
* We update the status here.
|
* We update the status here.
|
||||||
*
|
*
|
||||||
|
* @param seedURL the URL of the directory, must end in '/'
|
||||||
* @param echoStatus apparently always false
|
* @param echoStatus apparently always false
|
||||||
* @return count of routerinfos successfully fetched
|
* @return count of routerinfos successfully fetched
|
||||||
**/
|
**/
|
||||||
@ -416,6 +430,103 @@ public class Reseeder {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an su3 file containing routerInfo files
|
||||||
|
*
|
||||||
|
* We update the status here.
|
||||||
|
*
|
||||||
|
* @param seedURL the URL of the SU3 file
|
||||||
|
* @param echoStatus apparently always false
|
||||||
|
* @return count of routerinfos successfully fetched
|
||||||
|
* @since 0.9.14
|
||||||
|
**/
|
||||||
|
private int reseedSU3(String seedURL, boolean echoStatus) {
|
||||||
|
File contentRaw = null;
|
||||||
|
File zip = null;
|
||||||
|
File tmpDir = null;
|
||||||
|
try {
|
||||||
|
_checker.setStatus(_("Reseeding: fetching seed URL."));
|
||||||
|
System.err.println("Reseeding from " + seedURL);
|
||||||
|
URL dir = new URL(seedURL);
|
||||||
|
contentRaw = fetchURL(dir);
|
||||||
|
if (contentRaw == null) {
|
||||||
|
// Logging deprecated here since attemptFailed() provides better info
|
||||||
|
_log.warn("Failed reading seed URL: " + seedURL);
|
||||||
|
System.err.println("Reseed got no router infos from " + seedURL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
SU3File su3 = new SU3File(_context, contentRaw);
|
||||||
|
zip = new File(_context.getTempDir(), "reseed-" + _context.random().nextInt() + ".zip");
|
||||||
|
su3.verifyAndMigrate(zip);
|
||||||
|
int type = su3.getContentType();
|
||||||
|
if (type != SU3File.CONTENT_RESEED)
|
||||||
|
throw new IOException("Bad content type " + type);
|
||||||
|
tmpDir = new File(_context.getTempDir(), "reseeds-" + _context.random().nextInt());
|
||||||
|
if (!FileUtil.extractZip(zip, tmpDir))
|
||||||
|
throw new IOException("Bad zip file");
|
||||||
|
|
||||||
|
Hash ourHash = _context.routerHash();
|
||||||
|
String ourB64 = ourHash != null ? ROUTERINFO_PREFIX + ourHash.toBase64() + ROUTERINFO_SUFFIX : "";
|
||||||
|
|
||||||
|
File[] files = tmpDir.listFiles();
|
||||||
|
if (files == null || files.length == 0)
|
||||||
|
throw new IOException("No files in zip");
|
||||||
|
List<File> fList = Arrays.asList(files);
|
||||||
|
Collections.shuffle(fList, _context.random());
|
||||||
|
long minTime = _context.clock().now() - MAX_FILE_AGE;
|
||||||
|
int fetched = 0;
|
||||||
|
int errors = 0;
|
||||||
|
File netDbDir = new SecureDirectory(_context.getRouterDir(), "netDb");
|
||||||
|
if (!netDbDir.exists())
|
||||||
|
netDbDir.mkdirs();
|
||||||
|
|
||||||
|
// 400 max from one URL
|
||||||
|
for (Iterator<File> iter = fList.iterator(); iter.hasNext() && fetched < 400; ) {
|
||||||
|
File f = iter.next();
|
||||||
|
String name = f.getName();
|
||||||
|
if (name.length() != ROUTERINFO_PREFIX.length() + 44 + ROUTERINFO_SUFFIX.length() ||
|
||||||
|
name.equals(ourB64) ||
|
||||||
|
f.length() > 10*1024 ||
|
||||||
|
f.lastModified() < minTime ||
|
||||||
|
!f.isFile()) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Skipping " + f);
|
||||||
|
f.delete();
|
||||||
|
errors++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File to = new File(netDbDir, name);
|
||||||
|
if (FileUtil.rename(f, to)) {
|
||||||
|
fetched++;
|
||||||
|
} else {
|
||||||
|
f.delete();
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
// Give up on this host after lots of errors
|
||||||
|
if (errors >= 5)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_checker.setStatus(
|
||||||
|
_("Reseeding: fetching router info from seed URL ({0} successful, {1} errors).", fetched, errors));
|
||||||
|
System.err.println("Reseed got " + fetched + " router infos from " + seedURL + " with " + errors + " errors");
|
||||||
|
|
||||||
|
if (fetched > 0)
|
||||||
|
_context.netDb().rescan();
|
||||||
|
return fetched;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
_log.warn("Error reseeding", t);
|
||||||
|
System.err.println("Reseed got no router infos from " + seedURL);
|
||||||
|
return 0;
|
||||||
|
} finally {
|
||||||
|
if (contentRaw != null)
|
||||||
|
contentRaw.delete();
|
||||||
|
if (zip != null)
|
||||||
|
zip.delete();
|
||||||
|
if (tmpDir != null)
|
||||||
|
FileUtil.rmdir(tmpDir, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Always throws an exception if something fails.
|
* Always throws an exception if something fails.
|
||||||
@ -479,11 +590,53 @@ public class Reseeder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
get.addStatusListener(ReseedRunner.this);
|
get.addStatusListener(ReseedRunner.this);
|
||||||
if (get.fetch())
|
if (get.fetch() && get.getStatusCode() == 200)
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a URL to a file.
|
||||||
|
*
|
||||||
|
* @return null on error
|
||||||
|
* @since 0.9.14
|
||||||
|
*/
|
||||||
|
private File fetchURL(URL url) throws IOException {
|
||||||
|
File out = new File(_context.getTempDir(), "reseed-" + _context.random().nextInt() + ".tmp");
|
||||||
|
EepGet get;
|
||||||
|
boolean ssl = url.toString().startsWith("https");
|
||||||
|
if (ssl) {
|
||||||
|
SSLEepGet sslget;
|
||||||
|
// TODO SSL PROXY
|
||||||
|
if (_sslState == null) {
|
||||||
|
sslget = new SSLEepGet(I2PAppContext.getGlobalContext(), out.getPath(), url.toString());
|
||||||
|
// save state for next time
|
||||||
|
_sslState = sslget.getSSLState();
|
||||||
|
} else {
|
||||||
|
sslget = new SSLEepGet(I2PAppContext.getGlobalContext(), out.getPath(), url.toString(), _sslState);
|
||||||
|
}
|
||||||
|
get = sslget;
|
||||||
|
// TODO SSL PROXY AUTH
|
||||||
|
} else {
|
||||||
|
// Do a (probably) non-proxied eepget into file with 0 retries
|
||||||
|
boolean shouldProxy = _proxyHost != null && _proxyHost.length() > 0 && _proxyPort > 0;
|
||||||
|
get = new EepGet(I2PAppContext.getGlobalContext(), shouldProxy, _proxyHost, _proxyPort, 0, 0, MAX_SU3_RESPONSE_SIZE,
|
||||||
|
out.getPath(), null, url.toString(), false, null, null);
|
||||||
|
if (shouldProxy && _context.getBooleanProperty(PROP_PROXY_AUTH_ENABLE)) {
|
||||||
|
String user = _context.getProperty(PROP_PROXY_USERNAME);
|
||||||
|
String pass = _context.getProperty(PROP_PROXY_PASSWORD);
|
||||||
|
if (user != null && user.length() > 0 &&
|
||||||
|
pass != null && pass.length() > 0)
|
||||||
|
get.addAuthorization(user, pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get.addStatusListener(ReseedRunner.this);
|
||||||
|
if (get.fetch() && get.getStatusCode() == 200)
|
||||||
|
return out;
|
||||||
|
out.delete();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name valid Base64 hash
|
* @param name valid Base64 hash
|
||||||
* @return true on success, false if skipped
|
* @return true on success, false if skipped
|
||||||
|
Reference in New Issue
Block a user