Shitlist.java 11.34 KiB
package net.i2p.router;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.router.peermanager.PeerProfile;
import net.i2p.util.Log;
/**
* Routers are shitlisted only if none of our transports can talk to them
* or their signed router info is completely screwy. Individual transports
* manage their own unreachable lists and do not generally add to the overall
* shitlist.
*/
public class Shitlist {
private Log _log;
private RouterContext _context;
private Map _entries;
private class Entry {
/** when it should expire, per the i2p clock */
long expireOn;
/** why they were shitlisted */
String cause;
/** what transports they were shitlisted for (String), or null for all transports */
Set transports;
}
public final static long SHITLIST_DURATION_MS = 40*60*1000; // 40 minute shitlist
public final static long SHITLIST_DURATION_MAX = 60*60*1000;
public final static long SHITLIST_DURATION_PARTIAL = 20*60*1000;
public final static long SHITLIST_DURATION_FOREVER = 181l*24*60*60*1000; // will get rounded down to 180d on console
public Shitlist(RouterContext context) {
_context = context;
_log = context.logManager().getLog(Shitlist.class);
_entries = new HashMap(32);
_context.jobQueue().addJob(new Cleanup(_context));
}
private class Cleanup extends JobImpl {
private List _toUnshitlist;
public Cleanup(RouterContext ctx) {
super(ctx);
_toUnshitlist = new ArrayList(4);
}
public String getName() { return "Cleanup shitlist"; }
public void runJob() {
_toUnshitlist.clear();
long now = getContext().clock().now();
synchronized (_entries) {
for (Iterator iter = _entries.keySet().iterator(); iter.hasNext(); ) {
Hash peer = (Hash)iter.next();
Entry entry = (Entry)_entries.get(peer);
if (entry.expireOn <= now) {
iter.remove();
_toUnshitlist.add(peer);
}
}
}
for (int i = 0; i < _toUnshitlist.size(); i++) {
Hash peer = (Hash)_toUnshitlist.get(i);
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null)
prof.unshitlist();
_context.messageHistory().unshitlist(peer);
if (_log.shouldLog(Log.INFO))
_log.info("Unshitlisting router (expired) " + peer.toBase64());
}
requeue(30*1000);
}
}
public int getRouterCount() {
synchronized (_entries) {
return _entries.size();
}
}
public boolean shitlistRouter(Hash peer) {
return shitlistRouter(peer, null);
}
public boolean shitlistRouter(Hash peer, String reason) { return shitlistRouter(peer, reason, null); }
public boolean shitlistRouter(Hash peer, String reason, String transport) {
return shitlistRouter(peer, reason, transport, false);
}
public boolean shitlistRouterForever(Hash peer, String reason) {
return shitlistRouter(peer, reason, null, true);
}
public boolean shitlistRouter(Hash peer, String reason, String transport, boolean forever) {
if (peer == null) {
_log.error("wtf, why did we try to shitlist null?", new Exception("shitfaced"));
return false;
}
if (_context.routerHash().equals(peer)) {
_log.error("wtf, why did we try to shitlist ourselves?", new Exception("shitfaced"));
return false;
}
boolean wasAlready = false;
if (_log.shouldLog(Log.INFO))
_log.info("Shitlisting router " + peer.toBase64() +
((transport != null) ? " on transport " + transport : ""), new Exception("Shitlist cause: " + reason));
Entry e = new Entry();
if (forever) {
e.expireOn = _context.clock().now() + SHITLIST_DURATION_FOREVER;
else if (transport != null) {
e.expireOn = _context.clock().now() + SHITLIST_DURATION_PARTIAL;
} else {
long period = SHITLIST_DURATION_MS + _context.random().nextLong(SHITLIST_DURATION_MS);
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null) {
period = SHITLIST_DURATION_MS << prof.incrementShitlists();
period += _context.random().nextLong(period);
}
if (period > SHITLIST_DURATION_MAX)
period = SHITLIST_DURATION_MAX;
e.expireOn = _context.clock().now() + period;
}
e.cause = reason;
e.transports = null;
if (transport != null) {
e.transports = new HashSet(1);
e.transports.add(transport);
}
synchronized (_entries) {
Entry old = (Entry)_entries.get(peer);
if (old != null) {
wasAlready = true;
// take the oldest expiration and cause, combine transports
if (old.expireOn > e.expireOn) {
e.expireOn = old.expireOn;
e.cause = old.cause;
}
if (e.transports != null) {
if (old.transports != null)
e.transports.addAll(old.transports);
else {
e.transports = null;
e.cause = reason;
}
}
}
_entries.put(peer, e);
}
if (transport == null) {
// we hate the peer on *any* transport
_context.netDb().fail(peer);
}
//_context.tunnelManager().peerFailed(peer);
//_context.messageRegistry().peerFailed(peer);
if (!wasAlready)
_context.messageHistory().shitlist(peer, reason);
return wasAlready;
}
public void unshitlistRouter(Hash peer) {
unshitlistRouter(peer, true);
}
private void unshitlistRouter(Hash peer, boolean realUnshitlist) { unshitlistRouter(peer, realUnshitlist, null); }
public void unshitlistRouter(Hash peer, String transport) { unshitlistRouter(peer, true, transport); }
private void unshitlistRouter(Hash peer, boolean realUnshitlist, String transport) {
if (peer == null) return;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Calling unshitlistRouter " + peer.toBase64()
+ (transport != null ? "/" + transport : ""));
boolean fully = false;
Entry e;
synchronized (_entries) {
e = (Entry)_entries.remove(peer);
if ( (e == null) || (e.transports == null) || (transport == null) || (e.transports.size() <= 1) ) {
// fully unshitlisted
fully = true;
} else {
e.transports.remove(transport);
if (e.transports.size() <= 0)
fully = true;
else
_entries.put(peer, e);
}
}
if (fully) {
if (realUnshitlist) {
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null)
prof.unshitlist();
}
_context.messageHistory().unshitlist(peer);
if (_log.shouldLog(Log.INFO) && e != null)
_log.info("Unshitlisting router " + peer.toBase64()
+ (transport != null ? "/" + transport : ""));
}
}
public boolean isShitlisted(Hash peer) { return isShitlisted(peer, null); }
public boolean isShitlisted(Hash peer, String transport) {
boolean rv = false;
boolean unshitlist = false;
synchronized (_entries) {
Entry entry = (Entry)_entries.get(peer);
if (entry == null) {
rv = false;
} else {
if (entry.expireOn <= _context.clock().now()) {
_entries.remove(peer);
unshitlist = true;
rv = false;
} else {
if (entry.transports == null) {
rv = true;
} else if (entry.transports.contains(transport)) {
rv = true;
} else {
rv = false;
}
}
}
}
if (unshitlist) {
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null)
prof.unshitlist();
_context.messageHistory().unshitlist(peer);
if (_log.shouldLog(Log.INFO))
_log.info("Unshitlisting router (expired) " + peer.toBase64());
}
return rv;
}
public boolean isShitlistedForever(Hash peer) {
Entry entry;
synchronized (_entries) {
entry = (Entry)_entries.get(peer);
}
return entry != null && entry.expireOn > _context.clock().now() + SHITLIST_DURATION_MAX;
}
class HashComparator implements Comparator {
public int compare(Object l, Object r) {
return ((Hash)l).toBase64().compareTo(((Hash)r).toBase64());
}
}
public void renderStatusHTML(Writer out) throws IOException {
StringBuffer buf = new StringBuffer(1024);
buf.append("<h2>Shitlist</h2>");
Map entries = new TreeMap(new HashComparator());
synchronized (_entries) {
entries.putAll(_entries);
}
buf.append("<ul>");
for (Iterator iter = entries.keySet().iterator(); iter.hasNext(); ) {
Hash key = (Hash)iter.next();
Entry entry = (Entry)entries.get(key);
buf.append("<li><b>").append(key.toBase64()).append("</b>");
buf.append(" (<a href=\"netdb.jsp#").append(key.toBase64().substring(0, 6)).append("\">netdb</a>)");
buf.append(" expiring in ");
buf.append(DataHelper.formatDuration(entry.expireOn-_context.clock().now()));
Set transports = entry.transports;
if ( (transports != null) && (transports.size() > 0) )
buf.append(" on the following transport: ").append(transports);
if (entry.cause != null) {
buf.append("<br />\n");
buf.append(entry.cause);
}
buf.append(" (<a href=\"configpeer.jsp?peer=").append(key.toBase64()).append("#unsh\">unshitlist now</a>)");
buf.append("</li>\n");
}
buf.append("</ul>\n");
out.write(buf.toString());
out.flush();
}
}