Sybil: Persist blocklist

This commit is contained in:
zzz
2021-03-22 10:36:42 -04:00
parent 12c4f43109
commit 86b49546c8
2 changed files with 146 additions and 2 deletions

View File

@@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -16,6 +17,7 @@ import java.util.Set;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
@@ -24,6 +26,7 @@ import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.router.RouterKeyGenerator;
import net.i2p.router.Banlist;
import net.i2p.router.Blocklist;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings;
@@ -129,6 +132,38 @@ public class Analysis extends JobImpl implements RouterApp {
public PersistSybil getPersister() { return _persister; }
/**
* Load the persisted blocklist and tell the router
*
* @since 0.9.50
*/
private class InitJob extends JobImpl {
public InitJob() { super(_context); }
public String getName() { return "Load Sybil Blocklist"; }
public void runJob() {
Map<String, Long> map = _persister.readBlocklist();
if (map == null || map.isEmpty())
return;
Blocklist bl = _context.blocklist();
Banlist ban = _context.banlist();
for (Map.Entry<String, Long> e : map.entrySet()) {
String s = e.getKey();
if (s.contains(".") || s.contains(":")) {
bl.add(s);
} else {
byte[] b = Base64.decode(s);
if (b != null && b.length == Hash.HASH_LENGTH) {
Hash h = Hash.create(b);
long until = e.getValue().longValue();
ban.banlistRouter(h, "Sybil analysis", null, null, until);
}
}
}
}
}
/////// begin Job methods
public void runJob() {
@@ -159,6 +194,10 @@ public class Analysis extends JobImpl implements RouterApp {
changeState(RUNNING);
_cmgr.register(this);
_persister.removeOld();
InitJob init = new InitJob();
long start = _context.clock().now() + 5*1000;
init.getTiming().setStartAfter(start);
_context.jobQueue().addJob(init);
schedule();
}
@@ -395,16 +434,21 @@ public class Analysis extends JobImpl implements RouterApp {
threshold = MIN_BLOCK_POINTS;
} catch (NumberFormatException nfe) {}
String day = DataHelper.formatTime(now);
Set<String> blocks = new HashSet<String>();
for (Map.Entry<Hash, Points> e : points.entrySet()) {
double p = e.getValue().getPoints();
if (p >= threshold) {
Hash h = e.getKey();
blocks.add(h.toBase64());
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(h);
if (ri != null) {
for (RouterAddress ra : ri.getAddresses()) {
byte[] ip = ra.getIP();
if (ip != null)
_context.blocklist().add(ip);
_context.blocklist().add(ip);
String host = ra.getHost();
if (host != null)
blocks.add(host);
}
}
String reason = "Sybil analysis " + day + " with " + fmt.format(p) + " threat points";
@@ -417,6 +461,8 @@ public class Analysis extends JobImpl implements RouterApp {
_context.banlist().banlistRouter(h, reason, null, null, blockUntil);
}
}
if (!blocks.isEmpty())
_persister.storeBlocklist(blocks, blockUntil);
}
/**

View File

@@ -15,6 +15,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -41,9 +42,11 @@ public class PersistSybil {
private final I2PAppContext _context;
private final Log _log;
private static final String DIR = "sybil-analysis/results";
private static final String SDIR = "sybil-analysis";
private static final String DIR = SDIR + "/results";
private static final String PFX = "sybil-";
private static final String SFX = ".txt.gz";
private static final String BLOCKLIST_SYBIL_FILE = "blocklist-sybil.txt";
/** access via Analysis.getPersister() */
PersistSybil(I2PAppContext ctx) {
@@ -229,6 +232,101 @@ public class PersistSybil {
return file.delete();
}
/**
* Read the blocklist
*
* @return map of ip or hash to expiration (ms), or null on failure
* @since 0.9.50
*/
Map<String, Long> readBlocklist() {
File f = new File(_context.getConfigDir(), SDIR);
f = new File(f, BLOCKLIST_SYBIL_FILE);
return readBlocklist(f);
}
/**
* Read the blocklist
*
* @return map of ip or hash to expiration (ms), or null on failure
* @since 0.9.50
*/
private synchronized Map<String, Long> readBlocklist(File blFile) {
Map<String, Long> rv = null;
if (blFile.exists()) {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(
new FileInputStream(blFile), "UTF-8"));
rv = new HashMap<String, Long>();
String buf = null;
long now = _context.clock().now() + 5*60*1000L;
while ((buf = br.readLine()) != null) {
int index = buf.indexOf('#');
if (index == 0)
continue;
String[] ss = DataHelper.split(buf, ",", 2);
if (ss.length != 2)
continue;
try {
long exp = Long.parseLong(ss[1]);
if (exp < now)
continue;
rv.put(ss[0], Long.valueOf(exp));
} catch (NumberFormatException nfe) {}
}
} catch (IOException ioe) {
if (_log.shouldWarn())
_log.warn("Error reading the blocklist file", ioe);
} finally {
if (br != null) try { br.close(); } catch (IOException ioe) {}
}
}
return rv;
}
/**
* Write the blocklist.
* The format is different than other blocklists because we include an expiration.
* Format: One per line: ip or hash,expiration time (ms)
*
* @param blocks non-empty, will be merged with existing entries
* @since 0.9.50
*/
synchronized void storeBlocklist(Set<String> blocks, long blockUntil) {
File dir = new SecureDirectory(_context.getConfigDir(), SDIR);
if (!dir.exists())
dir.mkdirs();
File blFile = new File(dir, BLOCKLIST_SYBIL_FILE);
Map<String, Long> map = readBlocklist(blFile);
if (map == null)
map = new HashMap<String, Long>();
Long until = Long.valueOf(blockUntil);
for (String s : blocks) {
Long old = map.put(s, until);
if (old != null && old.longValue() > blockUntil) {
// unlikely
map.put(s, old);
}
}
Writer out = null;
try {
out = new OutputStreamWriter(new SecureFileOutputStream(blFile));
out.write("# Format (one per line)\n");
out.write("# IP or Base64 router hash,expiration (ms)\n");
for (Map.Entry<String, Long> e : map.entrySet()) {
out.write(e.getKey());
out.write(',');
out.write(e.getValue().toString());
out.write('\n');
}
} catch (IOException ioe) {
if (_log.shouldWarn())
_log.warn("Error writing the blocklist file", ioe);
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
/****
public static void main(String[] args) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();