package freenet.node; import static java.util.concurrent.TimeUnit.HOURS; import java.net.InetAddress; import java.util.Arrays; import java.util.Comparator; import java.util.Random; import freenet.l10n.NodeL10n; import freenet.support.HTMLNode; import freenet.support.LRUMap; import freenet.support.io.InetAddressComparator; /** Tracks announcements by IP address to identify nodes that announce repeatedly. */ public class SeedAnnounceTracker { private final LRUMap<InetAddress, TrackerItem> itemsByIP = LRUMap.createSafeMap(InetAddressComparator.COMPARATOR); // This should be plenty for now and limits memory usage to something reasonable. private static final int MAX_SIZE = 100*1000; /** A single IP address's behaviour */ private class TrackerItem { private TrackerItem(InetAddress addr) { this.addr = addr; } private final InetAddress addr; private int totalSeedConnects; private int totalAnnounceRequests; private int totalAcceptedAnnounceRequests; private int totalCompletedAnnounceRequests; private int totalSentRefs; private int lastVersion; public void acceptedAnnounce() { totalAnnounceRequests++; totalAcceptedAnnounceRequests++; } public void rejectedAnnounce() { totalAnnounceRequests++; } public void connected() { totalSeedConnects++; } public void setVersion(int ver) { if(ver <= 0) return; lastVersion = ver; } public void completed(int forwardedRefs) { totalCompletedAnnounceRequests++; totalSentRefs += forwardedRefs; } } // Reset every 2 hours. // FIXME implement something smoother. static final long RESET_TIME = HOURS.toMillis(2); private long lastReset; /** If the IP has had at least 5 noderefs, and is out of date, 80% chance of rejection. * If the IP has had 10 noderefs, 75% chance of rejection. */ public boolean acceptAnnounce(SeedClientPeerNode source, Random fastRandom) { InetAddress addr = source.getPeer().getAddress(); int ver = source.getVersionNumber(); boolean badVersion = source.isUnroutableOlderVersion(); long now = System.currentTimeMillis(); synchronized(this) { if(lastReset - now > RESET_TIME) { itemsByIP.clear(); lastReset = now; } TrackerItem item = itemsByIP.get(addr); if(item == null) { item = new TrackerItem(addr); } else { if(item.totalSentRefs > 5 && badVersion) { if(fastRandom.nextInt(5) != 0) return false; } else if(item.totalSentRefs > 10) { if(fastRandom.nextInt(4) != 0) return false; } } item.acceptedAnnounce(); item.setVersion(ver); itemsByIP.push(addr, item); while(itemsByIP.size() > MAX_SIZE) itemsByIP.popKey(); return true; } } public void rejectedAnnounce(SeedClientPeerNode source) { InetAddress addr = source.getPeer().getAddress(); int ver = source.getVersionNumber(); synchronized(this) { TrackerItem item = itemsByIP.get(addr); if(item == null) item = new TrackerItem(addr); item.rejectedAnnounce(); item.setVersion(ver); itemsByIP.push(addr, item); while(itemsByIP.size() > MAX_SIZE) itemsByIP.popKey(); } } public void onConnectSeed(SeedClientPeerNode source) { InetAddress addr = source.getPeer().getAddress(); int ver = source.getVersionNumber(); synchronized(this) { TrackerItem item = itemsByIP.get(addr); if(item == null) item = new TrackerItem(addr); item.connected(); item.setVersion(ver); itemsByIP.push(addr, item); while(itemsByIP.size() > MAX_SIZE) itemsByIP.popKey(); } } public void completedAnnounce(SeedClientPeerNode source, int forwardedRefs) { InetAddress addr = source.getPeer().getAddress(); int ver = source.getVersionNumber(); synchronized(this) { TrackerItem item = itemsByIP.get(addr); if(item == null) item = new TrackerItem(addr); item.completed(forwardedRefs); item.setVersion(ver); itemsByIP.push(addr, item); while(itemsByIP.size() > MAX_SIZE) itemsByIP.popKey(); } } public void drawSeedStats(HTMLNode content) { TrackerItem[] topItems = getTopTrackerItems(20); if(topItems.length == 0) return; HTMLNode table = content.addChild("table", "border", "0"); HTMLNode row = table.addChild("tr"); row.addChild("th", l10nStats("seedTableIP")); row.addChild("th", l10nStats("seedTableConnections")); row.addChild("th", l10nStats("seedTableAnnouncements")); row.addChild("th", l10nStats("seedTableAccepted")); row.addChild("th", l10nStats("seedTableCompleted")); row.addChild("th", l10nStats("seedTableForwarded")); row.addChild("th", l10nStats("seedTableVersion")); for(TrackerItem item : topItems) { row = table.addChild("tr"); row.addChild("td", item.addr.getHostAddress()); row.addChild("td", Integer.toString(item.totalSeedConnects)); row.addChild("td", Integer.toString(item.totalAnnounceRequests)); row.addChild("td", Integer.toString(item.totalAcceptedAnnounceRequests)); row.addChild("td", Integer.toString(item.totalCompletedAnnounceRequests)); row.addChild("td", Integer.toString(item.totalSentRefs)); row.addChild("td", Integer.toString(item.lastVersion)); } } private synchronized TrackerItem[] getTopTrackerItems(int count) { TrackerItem[] items = new TrackerItem[itemsByIP.size()]; itemsByIP.valuesToArray(items); Arrays.sort(items, new Comparator<TrackerItem>() { @Override public int compare(TrackerItem arg0, TrackerItem arg1) { int a = Math.max(arg0.totalAnnounceRequests, arg0.totalSeedConnects); int b = Math.max(arg1.totalAnnounceRequests, arg1.totalSeedConnects); if(a > b) return 1; if(b > a) return -1; if(arg0.totalAcceptedAnnounceRequests > arg1.totalAcceptedAnnounceRequests) return 1; else if(arg0.totalAcceptedAnnounceRequests < arg1.totalAcceptedAnnounceRequests) return -1; return 0; } }); int topLength = Math.min(count, items.length); return Arrays.copyOfRange(items, items.length - topLength, items.length); } private String l10nStats(String key) { return NodeL10n.getBase().getString("StatisticsToadlet."+key); } }