Compare commits

...

9 Commits

3 changed files with 335 additions and 226 deletions

View File

@ -269,11 +269,11 @@ public class SybilRenderer {
first = false;
}
buf.append('>').append(DataHelper.formatTime(date.longValue())).append("</option>\n");
}
}
buf.append("</select>\n" +
"<input type=\"submit\" name=\"action\" class=\"go\" value=\"Review analysis\" />" +
"</form>\n");
}
}
writeBuf(out, buf);
}
@ -543,7 +543,7 @@ public class SybilRenderer {
if (p < minDisplay)
break; // sorted
buf.append("<p class=\"threatpoints\"><b>Threat Points: " + fmt.format(p) + "</b></p><ul>");
List<String> reasons = pp.getReasons();
List<String> reasons = new ArrayList<>(pp.getReasons().keySet());
if (reasons.size() > 1)
Collections.sort(reasons, rcomp);
for (String s : reasons) {

View File

@ -49,7 +49,7 @@ import net.i2p.util.Translate;
/**
*
* @since 0.9.38 split out from SybilRenderer
* @since 0.9.38 split out from SybilRenderer
*
*/
public class Analysis extends JobImpl implements RouterApp, Runnable {
@ -64,7 +64,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
private final List<String> _familyExemptPoints24 = new ArrayList<String>(2);
/**
* The name we register with the ClientAppManager
* The name we register with the ClientAppManager
*/
public static final String APP_NAME = "sybil";
public static final String PROP_FREQUENCY = "router.sybilFrequency";
@ -73,8 +73,8 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
public static final String PROP_NONFF = "router.sybilAnalyzeAll";
public static final String PROP_BLOCKTIME = "router.sybilBlockPeriod";
public static final String PROP_REMOVETIME = "router.sybilDeleteOld";
private static final long MIN_FREQUENCY = 60*60*1000L;
private static final long MIN_UPTIME = 75*60*1000L;
private static final long MIN_FREQUENCY = 60 * 60 * 1000L;
private static final long MIN_UPTIME = 75 * 60 * 1000L;
public static final int PAIRMAX = 20;
public static final int MAX = 10;
@ -112,16 +112,22 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
private static final double POINTS_BANLIST = 10.0;
public static final boolean DEFAULT_BLOCK = true;
public static final double DEFAULT_BLOCK_THRESHOLD = 50.0;
public static final long DEFAULT_BLOCK_TIME = 7*24*60*60*1000L;
public static final long DEFAULT_REMOVE_TIME = 10*24*60*60*1000L;
public static final long SHORT_REMOVE_TIME = 2*24*60*60*1000L;
public static final long DEFAULT_FREQUENCY = 24*60*60*1000L;
public static final long DEFAULT_BLOCK_TIME = 7 * 24 * 60 * 60 * 1000L;
public static final long DEFAULT_REMOVE_TIME = 10 * 24 * 60 * 60 * 1000L;
public static final long SHORT_REMOVE_TIME = 2 * 24 * 60 * 60 * 1000L;
public static final long DEFAULT_FREQUENCY = 24 * 60 * 60 * 1000L;
public static final float MIN_BLOCK_POINTS = 12.01f;
private static final byte[] IPV6_LOCALHOST = new byte[16];
static { IPV6_LOCALHOST[15] = 1; }
static {
IPV6_LOCALHOST[15] = 1;
}
// i2pd bug 64:ff9b::/96
private static final byte[] IPV6_NAT64 = new byte[16];
static { IPV6_NAT64[1] = 0x64; IPV6_NAT64[2] = (byte) 0xff; IPV6_NAT64[3] = (byte) 0x9b; }
static {
IPV6_NAT64[1] = 0x64;
IPV6_NAT64[2] = (byte) 0xff;
IPV6_NAT64[3] = (byte) 0x9b;
}
/** Get via getInstance() */
private Analysis(RouterContext ctx, ClientAppManager mgr, String[] args) {
@ -135,7 +141,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @return non-null, creates new if not already registered
* @return non-null, creates new if not already registered
*/
public synchronized static Analysis getInstance(RouterContext ctx) {
ClientAppManager cmgr = ctx.clientAppManager();
@ -149,17 +155,23 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
return rv;
}
public PersistSybil getPersister() { return _persister; }
public PersistSybil getPersister() {
return _persister;
}
/**
* Load the persisted blocklist and tell the router
* Load the persisted blocklist and tell the router
*
* @since 0.9.50
* @since 0.9.50
*/
private class InitJob extends JobImpl {
public InitJob() { super(_context); }
public InitJob() {
super(_context);
}
public String getName() { return "Load Sybil Blocklist"; }
public String getName() {
return "Load Sybil Blocklist";
}
public void runJob() {
Map<String, Long> map = _persister.readBlocklist();
@ -196,7 +208,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.58
* @since 0.9.58
*/
public void run() {
long now = _context.clock().now();
@ -218,7 +230,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
/////// begin ClientApp methods
/**
* ClientApp interface
* ClientApp interface
*/
public synchronized void startup() {
changeState(STARTING);
@ -226,15 +238,16 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
_cmgr.register(this);
_persister.removeOld();
InitJob init = new InitJob();
long start = _context.clock().now() + 5*1000;
long start = _context.clock().now() + 5 * 1000;
init.getTiming().setStartAfter(start);
_context.jobQueue().addJob(init);
schedule();
}
/**
* ClientApp interface
* @param args ignored
* ClientApp interface
*
* @param args ignored
*/
public synchronized void shutdown(String[] args) {
if (_state == STOPPED)
@ -267,7 +280,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
long freq = _context.getProperty(PROP_FREQUENCY, DEFAULT_FREQUENCY);
if (freq > 0) {
List<Long> previous = _persister.load();
long now = _context.clock().now() + 15*1000;
long now = _context.clock().now() + 15 * 1000;
if (freq < MIN_FREQUENCY)
freq = MIN_FREQUENCY;
long when;
@ -288,35 +301,38 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
private static class RouterInfoRoutingKeyComparator implements Comparator<RouterInfo>, Serializable {
private final Hash _us;
/** @param us ROUTING KEY */
public RouterInfoRoutingKeyComparator(Hash us) {
_us = us;
}
public int compare(RouterInfo l, RouterInfo r) {
return HashDistance.getDistance(_us, l.getHash()).compareTo(HashDistance.getDistance(_us, r.getHash()));
private final Hash _us;
/** @param us ROUTING KEY */
public RouterInfoRoutingKeyComparator(Hash us) {
_us = us;
}
public int compare(RouterInfo l, RouterInfo r) {
return HashDistance.getDistance(_us, l.getHash()).compareTo(HashDistance.getDistance(_us, r.getHash()));
}
}
/**
* Merge points1 into points2.
* points1 is unmodified.
* Merge points1 into points2.
* points1 is unmodified.
*/
/****
private void mergePoints(Map<Hash, Points> points1, Map<Hash, Points> points2) {
for (Map.Entry<Hash, Points> e : points1.entrySet()) {
Hash h = e.getKey();
Points p1 = e.getValue();
Points p2 = points2.get(h);
if (p2 != null) {
p2.points += p1.points;
p2.reasons.addAll(p1.reasons);
} else {
points2.put(h, p1);
}
}
}
****/
/****
* private void mergePoints(Map<Hash, Points> points1, Map<Hash, Points>
* points2) {
* for (Map.Entry<Hash, Points> e : points1.entrySet()) {
* Hash h = e.getKey();
* Points p1 = e.getValue();
* Points p2 = points2.get(h);
* if (p2 != null) {
* p2.points += p1.points;
* p2.reasons.addAll(p1.reasons);
* } else {
* points2.put(h, p1);
* }
* }
* }
****/
/** */
private void addPoints(Map<Hash, Points> points, Hash h, double d, String reason) {
@ -329,32 +345,34 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* All the floodfills, not including us
* @since 0.9.38 split out from renderRouterInfoHTML
* All the floodfills, not including us
*
* @since 0.9.38 split out from renderRouterInfoHTML
*/
public List<RouterInfo> getFloodfills(Hash us) {
Set<Hash> ffs = _context.peerManager().getPeersByCapability('f');
List<RouterInfo> ris = new ArrayList<RouterInfo>(ffs.size());
for (Hash ff : ffs) {
if (ff.equals(us))
continue;
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(ff);
if (ri != null)
ris.add(ri);
if (ff.equals(us))
continue;
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(ff);
if (ri != null)
ris.add(ri);
}
return ris;
}
/**
* All the routers, not including us
* @since 0.9.41
* All the routers, not including us
*
* @since 0.9.41
*/
public List<RouterInfo> getAllRouters(Hash us) {
Set<RouterInfo> set = _context.netDb().getRouters();
List<RouterInfo> ris = new ArrayList<RouterInfo>(set.size());
for (RouterInfo ri : set) {
if (!ri.getIdentity().getHash().equals(us))
ris.add(ri);
ris.add(ri);
}
return ris;
}
@ -374,10 +392,11 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* Analyze threats. No output.
* Return separate maps for each cause instead?
* @param includeAll false for floodfills only
* @since 0.9.38
* Analyze threats. No output.
* Return separate maps for each cause instead?
*
* @param includeAll false for floodfills only
* @since 0.9.38
*/
public synchronized Map<Hash, Points> backgroundAnalysis(boolean includeAll) {
_wasRun = true;
@ -401,11 +420,11 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
// unused here, just for the console, so use the same for all of them
List<RouterInfo> dummy = new DummyList();
calculateIPGroupsUs(ris, points, dummy, dummy, dummy, dummy, dummy);
//calculateIPGroups32(ris, points);
//calculateIPGroups24(ris, points);
//calculateIPGroups16(ris, points);
//calculateIPGroups64(ris, points);
//calculateIPGroups48(ris, points);
// calculateIPGroups32(ris, points);
// calculateIPGroups24(ris, points);
// calculateIPGroups16(ris, points);
// calculateIPGroups64(ris, points);
// calculateIPGroups48(ris, points);
// Pairwise distance analysis
// O(n**2)
@ -433,14 +452,15 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
boolean isLocal = _context.clientManager().isLocal(client);
if (!isLocal)
continue;
if (! _context.clientManager().shouldPublishLeaseSet(client))
if (!_context.clientManager().shouldPublishLeaseSet(client))
continue;
LeaseSet ls = _context.netDb().lookupLeaseSetLocally(client);
if (ls == null)
continue;
Hash rkey = ls.getRoutingKey();
TunnelPool in = clientInboundPools.get(client);
String name = (in != null) ? DataHelper.escapeHTML(in.getSettings().getDestinationNickname()) : client.toBase64().substring(0,4);
String name = (in != null) ? DataHelper.escapeHTML(in.getSettings().getDestinationNickname())
: client.toBase64().substring(0, 4);
// closest to routing key today
calculateRouterInfo(rkey, name, ris, points);
// closest to routing key tomorrow
@ -457,27 +477,35 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.57
* @since 0.9.57
*/
private static class DummyList extends ArrayList<RouterInfo> {
public DummyList() { super(0); }
public DummyList() {
super(0);
}
@Override
public boolean add(RouterInfo ri) { return true; }
public boolean add(RouterInfo ri) {
return true;
}
}
/**
* Blocklist and Banlist if configured
* @since 0.9.41
* Blocklist and Banlist if configured
*
* @since 0.9.41
*/
private void doBlocking(Map<Hash, Points> points) {
double threshold = DEFAULT_BLOCK_THRESHOLD;
long now = _context.clock().now();
long blockUntil = _context.getProperty(Analysis.PROP_BLOCKTIME, DEFAULT_BLOCK_TIME) + now;
try {
threshold = Double.parseDouble(_context.getProperty(PROP_THRESHOLD, Double.toString(DEFAULT_BLOCK_THRESHOLD)));
threshold = Double
.parseDouble(_context.getProperty(PROP_THRESHOLD, Double.toString(DEFAULT_BLOCK_THRESHOLD)));
if (threshold < MIN_BLOCK_POINTS)
threshold = MIN_BLOCK_POINTS;
} catch (NumberFormatException nfe) {}
} catch (NumberFormatException nfe) {
}
String day = DataHelper.formatTime(now);
Set<String> blocks = new HashSet<String>();
for (Map.Entry<Hash, Points> e : points.entrySet()) {
@ -511,12 +539,12 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @param pairs out parameter, sorted
* @return average distance
* @since 0.9.38 split out from renderPairDistance()
* @param pairs out parameter, sorted
* @return average distance
* @since 0.9.38 split out from renderPairDistance()
*/
public double calculatePairDistance(List<RouterInfo> ris, Map<Hash, Points> points,
List<Pair> pairs) {
List<Pair> pairs) {
int sz = ris.size();
double total = 0;
for (int i = 0; i < sz; i++) {
@ -549,14 +577,14 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
double distance = biLog2(p.dist);
double point = MIN_CLOSE - distance;
if (point < 0)
break; // sorted;
break; // sorted;
point *= PAIR_DISTANCE_FACTOR;
String b2 = p.r2.getHash().toBase64();
addPoints(points, p.r1.getHash(), point, "Very close (" + fmt.format(distance) +
") to other " + other + " <a href=\"netdb?r=" + b2 + "\">" + b2 + "</a>");
addPoints(points, p.r1.getHash(), point, Points.REASON_TOO_CLOSE + " (" + fmt.format(distance) +
") to other " + other + " <a href=\"netdb?r=" + b2 + "\">" + b2 + "</a>");
String b1 = p.r1.getHash().toBase64();
addPoints(points, p.r2.getHash(), point, "Very close (" + fmt.format(distance) +
") to other " + other + " <a href=\"netdb?r=" + b1 + "\">" + b1 + "</a>");
addPoints(points, p.r2.getHash(), point, Points.REASON_TOO_CLOSE + " (" + fmt.format(distance) +
") to other " + other + " <a href=\"netdb?r=" + b1 + "\">" + b1 + "</a>");
}
return avg;
}
@ -585,6 +613,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
/**
* v6 only
*
* @since 0.9.57
*/
private static byte[] getIPv6(RouterInfo ri) {
@ -599,16 +628,16 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @param ri32 out parameter
* @param ri24 out parameter
* @param ri16 out parameter
* @param ri64 out parameter
* @param ri48 out parameter
* @since 0.9.38 split out from renderIPGroupsUs()
* @param ri32 out parameter
* @param ri24 out parameter
* @param ri16 out parameter
* @param ri64 out parameter
* @param ri48 out parameter
* @since 0.9.38 split out from renderIPGroupsUs()
*/
public void calculateIPGroupsUs(List<RouterInfo> ris, Map<Hash, Points> points,
List<RouterInfo> ri32, List<RouterInfo> ri24, List<RouterInfo> ri16,
List<RouterInfo> ri64, List<RouterInfo> ri48) {
List<RouterInfo> ri32, List<RouterInfo> ri24, List<RouterInfo> ri16,
List<RouterInfo> ri64, List<RouterInfo> ri48) {
RouterInfo us = _context.router().getRouterInfo();
byte[] ourIP = getIP(us);
if (ourIP == null) {
@ -632,35 +661,35 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
String reason48;
if (ourIP != null) {
reason32 = "Same IP as <a href=\"/netdb?ip=" +
Addresses.toString(ourIP) +
"&amp;sybil\">us</a>";
reason24 = "Same IPv4 /24 as <a href=\"/netdb?ip=" +
(ourIP[0] & 0xff) + '.' +
(ourIP[1] & 0xff) + '.' +
(ourIP[2] & 0xff) +
".0/24&amp;sybil\">us</a>";
reason16 = "Same IPv4 /16 as <a href=\"/netdb?ip=" +
(ourIP[0] & 0xff) + '.' +
(ourIP[1] & 0xff) +
".0.0/16&amp;sybil\">us</a>";
reason32 = Points.REASON_SAME_IP4 + " as <a href=\"/netdb?ip=" +
Addresses.toString(ourIP) +
"&amp;sybil\">us</a>";
reason24 = Points.REASON_SAME_IP4_24 + " as <a href=\"/netdb?ip=" +
(ourIP[0] & 0xff) + '.' +
(ourIP[1] & 0xff) + '.' +
(ourIP[2] & 0xff) +
".0/24&amp;sybil\">us</a>";
reason16 = Points.REASON_SAME_IP4_16 + " as <a href=\"/netdb?ip=" +
(ourIP[0] & 0xff) + '.' +
(ourIP[1] & 0xff) +
".0.0/16&amp;sybil\">us</a>";
} else {
reason32 = null;
reason24 = null;
reason16 = null;
}
if (ourIPv6 != null) {
reason64 = "Same IPv6 /64 as <a href=\"/netdb?ip=" +
Integer.toString(((ourIPv6[0] << 8) & 0xff00) | (ourIPv6[1] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[2] << 8) & 0xff00) | (ourIPv6[3] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[4] << 8) & 0xff00) | (ourIPv6[5] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[6] << 8) & 0xff00) | (ourIPv6[7] & 0xff), 16) +
"::&amp;sybil\">us</a>";
reason48 = "Same IPv6 /48 as <a href=\"/netdb?ip=" +
Integer.toString(((ourIPv6[0] << 8) & 0xff00) | (ourIPv6[1] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[2] << 8) & 0xff00) | (ourIPv6[3] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[4] << 8) & 0xff00) | (ourIPv6[5] & 0xff), 16) +
"::&amp;sybil\">us</a>";
reason64 = Points.REASON_SAME_IP6_64 + " as <a href=\"/netdb?ip=" +
Integer.toString(((ourIPv6[0] << 8) & 0xff00) | (ourIPv6[1] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[2] << 8) & 0xff00) | (ourIPv6[3] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[4] << 8) & 0xff00) | (ourIPv6[5] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[6] << 8) & 0xff00) | (ourIPv6[7] & 0xff), 16) +
"::&amp;sybil\">us</a>";
reason48 = Points.REASON_SAME_IP6_48 + " as <a href=\"/netdb?ip=" +
Integer.toString(((ourIPv6[0] << 8) & 0xff00) | (ourIPv6[1] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[2] << 8) & 0xff00) | (ourIPv6[3] & 0xff), 16) + ':' +
Integer.toString(((ourIPv6[4] << 8) & 0xff00) | (ourIPv6[5] & 0xff), 16) +
"::&amp;sybil\">us</a>";
} else {
reason64 = null;
reason48 = null;
@ -703,7 +732,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.38 split out from renderIPGroups32()
* @since 0.9.38 split out from renderIPGroups32()
*/
public Map<Integer, List<RouterInfo>> calculateIPGroups32(List<RouterInfo> ris, Map<Hash, Points> points) {
ObjectCounterUnsafe<Integer> oc = new ObjectCounterUnsafe<Integer>();
@ -729,9 +758,9 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int i1 = (i >> 16) & 0xff;
int i2 = (i >> 8) & 0xff;
int i3 = i & 0xff;
String reason = "Same IP with <a href=\"/netdb?ip=" +
i0 + '.' + i1 + '.' + i2 + '.' + i3 + "&amp;sybil\">" +
(count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
String reason = Points.REASON_SAME_IP4 + " with <a href=\"/netdb?ip=" +
i0 + '.' + i1 + '.' + i2 + '.' + i3 + "&amp;sybil\">" +
(count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
for (RouterInfo info : ris) {
byte[] ip = getIP(info);
if (ip == null)
@ -752,7 +781,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.38 split out from renderIPGroups24()
* @since 0.9.38 split out from renderIPGroups24()
*/
public Map<Integer, List<RouterInfo>> calculateIPGroups24(List<RouterInfo> ris, Map<Hash, Points> points) {
ObjectCounterUnsafe<Integer> oc = new ObjectCounterUnsafe<Integer>();
@ -778,9 +807,9 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int i0 = i >> 16;
int i1 = (i >> 8) & 0xff;
int i2 = i & 0xff;
String reason = "Same IPv4 /24 with <a href=\"/netdb?ip=" +
i0 + '.' + i1 + '.' + i2 + ".0/24&amp;sybil\">" +
(count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
String reason = Points.REASON_SAME_IP4_24 + " with <a href=\"/netdb?ip=" +
i0 + '.' + i1 + '.' + i2 + ".0/24&amp;sybil\">" +
(count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
for (RouterInfo info : ris) {
byte[] ip = getIP(info);
if (ip == null)
@ -794,7 +823,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
if (fkc != null) {
String f = info.getOption("family");
if (f != null && _familyExemptPoints24.contains(f) &&
fkc.verify(info) == FamilyKeyCrypto.Result.STORED_KEY)
fkc.verify(info) == FamilyKeyCrypto.Result.STORED_KEY)
continue;
}
e.getValue().add(info);
@ -805,7 +834,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.38 split out from renderIPGroups16()
* @since 0.9.38 split out from renderIPGroups16()
*/
public Map<Integer, List<RouterInfo>> calculateIPGroups16(List<RouterInfo> ris, Map<Hash, Points> points) {
ObjectCounterUnsafe<Integer> oc = new ObjectCounterUnsafe<Integer>();
@ -829,9 +858,9 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int i = ii.intValue();
int i0 = i >> 8;
int i1 = i & 0xff;
String reason = "Same IPv4 /16 with <a href=\"/netdb?ip=" +
i0 + '.' + i1 + ".0.0/16&amp;sybil\">" +
(count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
String reason = Points.REASON_SAME_IP4_16 + " with <a href=\"/netdb?ip=" +
i0 + '.' + i1 + ".0.0/16&amp;sybil\">" +
(count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
for (RouterInfo info : ris) {
byte[] ip = getIP(info);
if (ip == null)
@ -848,7 +877,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.57
* @since 0.9.57
*/
public Map<Long, List<RouterInfo>> calculateIPGroups64(List<RouterInfo> ris, Map<Hash, Points> points) {
ObjectCounterUnsafe<Long> oc = new ObjectCounterUnsafe<Long>();
@ -878,13 +907,13 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int i5 = (int) ((i >> 16) & 0xff);
int i6 = (int) ((i >> 8) & 0xff);
int i7 = (int) (i & 0xff);
String reason = "Same IPv6 /64 with <a href=\"/netdb?ip=" +
Integer.toString((i0 << 8) | i1, 16) + ':' +
Integer.toString((i2 << 8) | i3, 16) + ':' +
Integer.toString((i4 << 8) | i5, 16) + ':' +
Integer.toString((i6 << 8) | i7, 16) +
"::&amp;sybil\">" +
(count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
String reason = Points.REASON_SAME_IP6_64 + " with <a href=\"/netdb?ip=" +
Integer.toString((i0 << 8) | i1, 16) + ':' +
Integer.toString((i2 << 8) | i3, 16) + ':' +
Integer.toString((i4 << 8) | i5, 16) + ':' +
Integer.toString((i6 << 8) | i7, 16) +
"::&amp;sybil\">" +
(count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
for (RouterInfo info : ris) {
byte[] ip = getIPv6(info);
if (ip == null)
@ -913,7 +942,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @since 0.9.57
* @since 0.9.57
*/
public Map<Long, List<RouterInfo>> calculateIPGroups48(List<RouterInfo> ris, Map<Hash, Points> points) {
ObjectCounterUnsafe<Long> oc = new ObjectCounterUnsafe<Long>();
@ -941,12 +970,12 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int i3 = (int) ((i >> 16) & 0xff);
int i4 = (int) ((i >> 8) & 0xff);
int i5 = (int) (i & 0xff);
String reason = "Same IPv6 /48 with <a href=\"/netdb?ip=" +
Integer.toString((i0 << 8) | i1, 16) + ':' +
Integer.toString((i2 << 8) | i3, 16) + ':' +
Integer.toString((i4 << 8) | i5, 16) +
"::&amp;sybil\">" +
(count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
String reason = Points.REASON_SAME_IP6_48 + " with <a href=\"/netdb?ip=" +
Integer.toString((i0 << 8) | i1, 16) + ':' +
Integer.toString((i2 << 8) | i3, 16) + ':' +
Integer.toString((i4 << 8) | i5, 16) +
"::&amp;sybil\">" +
(count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
for (RouterInfo info : ris) {
byte[] ip = getIPv6(info);
if (ip == null)
@ -971,8 +1000,8 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* @return map of family name to list of routers in that family
* @since 0.9.38 split out from renderIPGroupsFamily()
* @return map of family name to list of routers in that family
* @since 0.9.38 split out from renderIPGroupsFamily()
*/
public Map<String, List<RouterInfo>> calculateIPGroupsFamily(List<RouterInfo> ris, Map<Hash, Points> points) {
Map<String, List<RouterInfo>> rv = new HashMap<String, List<RouterInfo>>();
@ -1001,10 +1030,13 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
if (s.equals(ourFamily)) {
if (fkc.verifyOurFamily(info)) {
point = POINTS_OUR_FAMILY;
reason = "Our family \"" + ss + "\" with <a href=\"/netdb?fam=" + ss + "&amp;sybil\">" + (count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
reason = Points.REASON_MY_FAMILY + " \"" + ss + "\" with <a href=\"/netdb?fam=" + ss
+ "&amp;sybil\">"
+ (count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
} else {
point = POINTS_BAD_OUR_FAMILY;
reason = "Spoofed our family \"" + ss + "\" with <a href=\"/netdb?fam=" + ss + "&amp;sybil\">" + (count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
reason = Points.REASON_SPOOFED_MY_FAMILY + " \"" + ss + "\" with <a href=\"/netdb?fam=" + ss
+ "&amp;sybil\">" + (count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
}
} else {
FamilyKeyCrypto.Result r = fkc.verify(info);
@ -1014,13 +1046,16 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
case INVALID_SIG:
case NO_SIG:
point = POINTS_BAD_FAMILY;
reason = "Bad family config \"" + ss + '"';
reason = Points.REASON_INVALID_FAMILY + " \"" + ss + '"';
break;
case STORED_KEY:
point = POINTS_FAMILY_VERIFIED;
if (count > 1)
reason = "In verified family \"" + ss + "\" with <a href=\"/netdb?fam=" + ss + "&amp;sybil\">" + (count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
reason = Points.REASON_KNOWN_FAMILY + " \"" + ss + "\" with <a href=\"/netdb?fam="
+ ss
+ "&amp;sybil\">" + (count - 1) + " other" + ((count > 2) ? "s" : "")
+ "</a>";
else
reason = "In verified family \"" + ss + '"';
break;
@ -1030,22 +1065,27 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
case UNSUPPORTED_SIG:
case NAME_CHANGED:
case SIG_CHANGED:
case NO_FAMILY: // won't happen
case NO_FAMILY: // won't happen
default:
point = POINTS_FAMILY;
if (count > 1)
reason = "In unverified family \"" + ss + "\" with <a href=\"/netdb?fam=" + ss + "&amp;sybil\">" + (count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
reason = Points.REASON_VALID_FAMILY + " \"" + ss + "\" with <a href=\"/netdb?fam="
+ ss
+ "&amp;sybil\">" + (count - 1) + " other" + ((count > 2) ? "s" : "")
+ "</a>";
else
reason = "In unverified family \"" + ss + '"';
reason = Points.REASON_VALID_FAMILY + " \"" + ss + '"';
break;
}
}
} else if (count > 1) {
point = POINTS_FAMILY;
reason = "In unverified family \"" + ss + "\" with <a href=\"/netdb?fam=" + ss + "&amp;sybil\">" + (count - 1) + " other" + (( count > 2) ? "s" : "") + "</a>";
reason = Points.REASON_VALID_FAMILY + " \"" + ss + "\" with <a href=\"/netdb?fam=" + ss
+ "&amp;sybil\">"
+ (count - 1) + " other" + ((count > 2) ? "s" : "") + "</a>";
} else {
point = POINTS_FAMILY;
reason = "In unverified family \"" + ss + '"';
reason = Points.REASON_VALID_FAMILY + " \"" + ss + '"';
}
addPoints(points, info.getHash(), point, reason);
}
@ -1053,7 +1093,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
return rv;
}
private static final long DAY = 24*60*60*1000L;
private static final long DAY = 24 * 60 * 60 * 1000L;
public void addProfilePoints(List<RouterInfo> ris, Map<Hash, Points> points) {
Map<Hash, Banlist.Entry> banEntries = _context.banlist().getEntries();
@ -1062,7 +1102,7 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
for (RouterInfo info : ris) {
Hash h = info.getHash();
if (_context.banlist().isBanlisted(h)) {
StringBuilder buf = new StringBuilder("Banlisted");
StringBuilder buf = new StringBuilder(Points.REASON_BANLISTED);
Banlist.Entry entry = banEntries.get(h);
if (entry != null) {
if (entry.cause != null) {
@ -1087,20 +1127,21 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
// (POINTS_NEW / 48) for every hour under 48, max POINTS_NEW
double point = Math.min(POINTS_NEW, (2 * DAY - age) / (2 * DAY / POINTS_NEW));
addPoints(points, h, point,
"First heard about: " + _t("{0} ago", DataHelper.formatDuration2(age)));
Points.REASON_CONTACT + " " + _t("{0} ago", DataHelper.formatDuration2(age)));
}
}
DBHistory dbh = prof.getDBHistory();
if (dbh != null) {
RateStat rs = dbh.getFailedLookupRate();
if (rs != null) {
Rate r = rs.getRate(24*60*60*1000);
Rate r = rs.getRate(24 * 60 * 60 * 1000);
if (r != null) {
r.computeAverages(ra, false);
if (ra.getTotalEventCount() > 0) {
double avg = 100 * ra.getAverage();
if (avg > 40)
addPoints(points, h, (avg - 40) / 6.0, "Lookup fail rate " + ((int) avg) + '%');
addPoints(points, h, (avg - 40) / 6.0,
Points.REASON_ALWAYS_FAIL_LOOKUP + " " + ((int) avg) + '%');
}
}
}
@ -1111,10 +1152,12 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
public void addVersionPoints(List<RouterInfo> ris, Map<Hash, Points> points) {
RouterInfo us = _context.router().getRouterInfo();
if (us == null) return;
if (us == null)
return;
String ourVer = us.getVersion();
// TODO do the math once we hit version 1.0.0
if (!ourVer.startsWith("0.9.")) return;
if (!ourVer.startsWith("0.9."))
return;
ourVer = ourVer.substring(4);
int dot = ourVer.indexOf('.');
if (dot > 0)
@ -1122,18 +1165,21 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int minor;
try {
minor = Integer.parseInt(ourVer);
} catch (NumberFormatException nfe) { return; }
} catch (NumberFormatException nfe) {
return;
}
for (RouterInfo info : ris) {
Hash h = info.getHash();
String caps = info.getCapabilities();
if (!caps.contains("R"))
addPoints(points, h, POINTS_UNREACHABLE, "Unreachable: " + DataHelper.escapeHTML(caps));
addPoints(points, h, POINTS_UNREACHABLE, Points.REASON_UNREACHABLE + DataHelper.escapeHTML(caps));
if (!caps.contains("f"))
addPoints(points, h, POINTS_NONFF, "Non-floodfill");
addPoints(points, h, POINTS_NONFF, Points.REASON_NON_FLOODFILL);
String hisFullVer = info.getVersion();
if (!hisFullVer.startsWith("0.9.")) {
if (!hisFullVer.startsWith("1."))
addPoints(points, h, POINTS_BAD_VERSION, "Strange version " + DataHelper.escapeHTML(hisFullVer));
addPoints(points, h, POINTS_BAD_VERSION,
Points.REASON_BAD_VERSION + DataHelper.escapeHTML(hisFullVer));
// TODO do the math once we hit version 1.0.0
continue;
}
@ -1144,21 +1190,24 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
int hisMinor;
try {
hisMinor = Integer.parseInt(hisVer);
} catch (NumberFormatException nfe) { continue; }
} catch (NumberFormatException nfe) {
continue;
}
int howOld = minor - hisMinor;
if (howOld < 3)
continue;
addPoints(points, h, howOld * VERSION_FACTOR, howOld + " versions behind: " + DataHelper.escapeHTML(hisFullVer));
addPoints(points, h, howOld * VERSION_FACTOR,
Points.REASON_OLD_VERSION + howOld + " versions behind: " + DataHelper.escapeHTML(hisFullVer));
}
}
/**
* @param usName HTML escaped
* @param ris will be re-sorted in place
* @since 0.9.38 split out from renderRouterInfoHTML()
* @param usName HTML escaped
* @param ris will be re-sorted in place
* @since 0.9.38 split out from renderRouterInfoHTML()
*/
public void calculateRouterInfo(Hash us, String usName,
List<RouterInfo> ris, Map<Hash, Points> points) {
List<RouterInfo> ris, Map<Hash, Points> points) {
Collections.sort(ris, new RouterInfoRoutingKeyComparator(us));
int count = Math.min(MAX, ris.size());
for (int i = 0; i < count; i++) {
@ -1172,13 +1221,15 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
if (point <= 0)
break;
point *= OUR_KEY_FACTOR;
addPoints(points, ri.getHash(), point, "Very close (" + fmt.format(dist) + ") to our key " + usName + ": " + us.toBase64());
addPoints(points, ri.getHash(), point,
Points.REASON_TOO_CLOSE + "(" + fmt.format(dist) + ") to our key " + usName + ": " + us.toBase64());
}
}
/**
* For debugging
* http://forums.sun.com/thread.jspa?threadID=597652
*
* @since 0.7.14
*/
private static double biLog2(BigInteger a) {
@ -1192,16 +1243,16 @@ public class Analysis extends JobImpl implements RouterApp, Runnable {
}
/**
* translate a string with a parameter
* This is a lot more expensive than _t(s), so use sparingly.
* translate a string with a parameter
* This is a lot more expensive than _t(s), so use sparingly.
*
* @param s string to be translated containing {0}
* The {0} will be replaced by the parameter.
* Single quotes must be doubled, i.e. ' -> '' in the string.
* @param o parameter, not translated.
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
* Do not double the single quotes in the parameter.
* Use autoboxing to call with ints, longs, floats, etc.
* @param s string to be translated containing {0}
* The {0} will be replaced by the parameter.
* Single quotes must be doubled, i.e. ' -> '' in the string.
* @param o parameter, not translated.
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
* Do not double the single quotes in the parameter.
* Use autoboxing to call with ints, longs, floats, etc.
*/
private String _t(String s, Object o) {
return Translate.getString(s, o, _context, BUNDLE_NAME);

View File

@ -3,27 +3,53 @@ package net.i2p.router.sybil;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.DataHelper;
/**
* A total score and a List of reason Strings
* A total score and a List of reason Strings
*
* @since 0.9.38 moved from SybilRenderer
* @since 0.9.38 moved from SybilRenderer
*/
public class Points implements Comparable<Points> {
private double points;
private final List<String> reasons;
private final Map<String, Double> reasons;
public static final String REASON_NON_FLOODFILL = "Non-floodfill: ";
public static final String REASON_TOO_CLOSE = "Very close: ";
public static final String REASON_ALWAYS_FAIL_LOOKUP = "Lookup fail rate: ";
public static final String REASON_UNREACHABLE = "Unreachable: ";
public static final String REASON_BAD_VERSION = "Strange Version: ";
public static final String REASON_OLD_VERSION = "Old Version: ";
public static final String REASON_MY_FAMILY = "Our family: ";
public static final String REASON_SPOOFED_MY_FAMILY = "Spoofed my family: ";
public static final String REASON_KNOWN_FAMILY = "Verified family: ";
public static final String REASON_INVALID_FAMILY = "Invalid family: ";
public static final String REASON_VALID_FAMILY = "Valid Unverified family: ";
public static final String REASON_SAME_IP4 = "Same IPv4: ";
public static final String REASON_SAME_IP4_16 = "Same IPv4/16: ";
public static final String REASON_SAME_IP4_24 = "Same IPv4/24: ";
public static final String REASON_SAME_IP6 = "Same IPv6: ";
public static final String REASON_SAME_IP6_48 = "Same IPv6/48: ";
public static final String REASON_SAME_IP6_64 = "Same IPv6/64: ";
public static final String REASON_BANLISTED = "Banlisted: ";
public static final String REASON_CONTACT = "First Heard About: ";
/**
* @since 0.9.38
* @since 0.9.38
*/
private Points() {
reasons = new ArrayList<String>(4);
reasons = new ConcurrentHashMap<String, Double>(4);
}
/**
* @param reason may not contain '%'
* @param reason may not contain '%' or '\t'
*/
public Points(double d, String reason) {
this();
@ -31,36 +57,68 @@ public class Points implements Comparable<Points> {
}
/**
* @since 0.9.38
* Compare 2 reason strings by comparing them to the list of static constants above.
* Ignores anything following the `:` when comparing the strings.
*
* @param comparator
* @param base
* @return
*/
public double getPoints() {
return points;
public static boolean compareReason(String comparator, String base) {
String[] comparatorPrefix = DataHelper.split(comparator, ":");
String[] basePrefix = DataHelper.split(base, ":");
if (comparatorPrefix == null)
return false;
if (basePrefix == null)
return false;
return basePrefix[0].toLowerCase().equals(comparatorPrefix[0].toLowerCase());
}
private double points() {
double rv = 0;
for (String reason : reasons.keySet()) {
rv += reasons.get(reason);
}
return rv;
}
/**
* @since 0.9.38
* @since 0.9.38
*/
public List<String> getReasons() {
public double getPoints() {
return points();
}
/**
* @since 0.9.38
*/
public Map<String, Double> getReasons() {
return reasons;
}
/**
* @param reason may not contain '%'
* @since 0.9.38
* @param reason may not contain '%' or '\t'
* @since 0.9.38
*/
public void addPoints(double d, String reason) {
points += d;
DecimalFormat format = new DecimalFormat("#0.00");
String rsn = format.format(d) + ": " + reason;
reasons.add(rsn);
Double rp = reasons.get(rsn);
if (rp == null) {
// reason was not yet present in the map, create a new entry for it.
reasons.put(rsn, d);
} else {
// reason was present in the map, add the points to it.
rp += d;
}
}
public int compareTo(Points r) {
return Double.compare(points, r.points);
return Double.compare(points(), r.points());
}
/**
* @since 0.9.38
* @since 0.9.38
*/
@Override
public String toString() {
@ -70,42 +128,42 @@ public class Points implements Comparable<Points> {
}
/**
* For persistence.
* Total points and reasons, '%' separated, no newline.
* The separation character is chosen to not conflict with
* decimal point in various locales, or chars in reasons, including HTML links,
* or special chars in Pattern.
* For persistence.
* points and reasons, '%' separated, new line between pairs.
* The separation character is chosen to not conflict with
* decimal point in various locales, or chars in reasons, including HTML links,
* or special chars in Pattern.
*
* @since 0.9.38
* Format changed in 0.9.64
*
* @since 0.9.38
*/
public void toString(StringBuilder buf) {
buf.append(points);
for (String r : reasons) {
buf.append('%').append(r.replace("%", "&#x25;"));
for (String r : reasons.keySet()) {
buf.append(reasons.get(r)).append('%').append(r.replace("%", "&#x25;")).append("\t");
}
}
/**
* For persistence.
* @return null on failure
* @since 0.9.38
* For persistence.
*
* @return null on failure
* @since 0.9.38
*/
public static Points fromString(String s) {
String[] ss = DataHelper.split(s, "%");
if (ss.length < 2)
return null;
double d;
try {
d = Double.parseDouble(ss[0]);
} catch (NumberFormatException nfe) {
return null;
}
Points rv = new Points();
for (int i = 1; i < ss.length; i++) {
rv.reasons.add(ss[i]);
for (String lineString : DataHelper.split(s, "\t")) {
String[] ss = DataHelper.split(lineString, "%");
if (ss.length != 2)
return null;
double d;
try {
d = Double.parseDouble(ss[0]);
} catch (NumberFormatException nfe) {
return null;
}
rv.reasons.put(ss[1], d);
}
rv.points = d;
return rv;
}
}