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("