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.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.Hash; import net.i2p.router.peermanager.PeerProfile; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; /** * Routers are banlisted 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 * banlist. */ public class Banlist { private final Log _log; private final RouterContext _context; private final Map<Hash, Entry> _entries; public static class Entry { /** when it should expire, per the i2p clock */ public long expireOn; /** why they were banlisted */ public String cause; /** separate code so cause can contain {0} for translation */ public String causeCode; /** what transports they were banlisted for (String), or null for all transports */ public Set<String> transports; } /** * Don't make this too long as the failure may be transient * due to connection limits. */ public final static long BANLIST_DURATION_MS = 7*60*1000; public final static long BANLIST_DURATION_MAX = 30*60*1000; public final static long BANLIST_DURATION_PARTIAL = 10*60*1000; public final static long BANLIST_DURATION_FOREVER = 181l*24*60*60*1000; // will get rounded down to 180d on console public final static long BANLIST_CLEANER_START_DELAY = BANLIST_DURATION_PARTIAL; public Banlist(RouterContext context) { _context = context; _log = context.logManager().getLog(Banlist.class); _entries = new ConcurrentHashMap<Hash, Entry>(16); _context.jobQueue().addJob(new Cleanup(_context)); // i2pd bug? banlistRouterForever(Hash.FAKE_HASH, "Invalid Hash"); } private class Cleanup extends JobImpl { private List<Hash> _toUnbanlist; public Cleanup(RouterContext ctx) { super(ctx); _toUnbanlist = new ArrayList<Hash>(4); getTiming().setStartAfter(ctx.clock().now() + BANLIST_CLEANER_START_DELAY); } public String getName() { return "Expire banned peers"; } public void runJob() { _toUnbanlist.clear(); long now = getContext().clock().now(); try { for (Iterator<Map.Entry<Hash, Entry>> iter = _entries.entrySet().iterator(); iter.hasNext(); ) { Map.Entry<Hash, Entry> e = iter.next(); if (e.getValue().expireOn <= now) { iter.remove(); _toUnbanlist.add(e.getKey()); } } } catch (IllegalStateException ise) {} // next time... for (Hash peer : _toUnbanlist) { //PeerProfile prof = _context.profileOrganizer().getProfile(peer); //if (prof != null) // prof.unbanlist(); _context.messageHistory().unbanlist(peer); if (_log.shouldLog(Log.INFO)) _log.info("Unbanlisting router (expired) " + peer.toBase64()); } requeue(30*1000); } } public int getRouterCount() { return _entries.size(); } /** * For BanlistRenderer in router console. * Note - may contain expired entries. */ public Map<Hash, Entry> getEntries() { return Collections.unmodifiableMap(_entries); } public boolean banlistRouter(Hash peer) { return banlistRouter(peer, null); } public boolean banlistRouter(Hash peer, String reason) { return banlistRouter(peer, reason, null); } /** ick have to put the reasonCode in the front to avoid ambiguity */ public boolean banlistRouter(String reasonCode, Hash peer, String reason) { return banlistRouter(peer, reason, reasonCode, null, false); } public boolean banlistRouter(Hash peer, String reason, String transport) { return banlistRouter(peer, reason, transport, false); } public boolean banlistRouterForever(Hash peer, String reason) { return banlistRouter(peer, reason, null, true); } public boolean banlistRouterForever(Hash peer, String reason, String reasonCode) { return banlistRouter(peer, reason, reasonCode, null, true); } public boolean banlistRouter(Hash peer, String reason, String transport, boolean forever) { return banlistRouter(peer, reason, null, transport, forever); } private boolean banlistRouter(Hash peer, String reason, String reasonCode, String transport, boolean forever) { long expireOn; if (forever) { expireOn = _context.clock().now() + BANLIST_DURATION_FOREVER; } else if (transport != null) { expireOn = _context.clock().now() + BANLIST_DURATION_PARTIAL; } else { long period = BANLIST_DURATION_MS + _context.random().nextLong(BANLIST_DURATION_MS / 4); if (period > BANLIST_DURATION_MAX) period = BANLIST_DURATION_MAX; expireOn = _context.clock().now() + period; } return banlistRouter(peer, reason, reasonCode, transport, expireOn); } /** * So that we may specify an expiration * * @param reason may be null * @param reasonCode may be null * @param expireOn absolute time, not a duration * @param transport may be null * @since 0.9.18 */ public boolean banlistRouter(Hash peer, String reason, String reasonCode, String transport, long expireOn) { if (peer == null) { _log.error("ban null?", new Exception()); return false; } if (peer.equals(_context.routerHash())) { if (_log.shouldWarn()) _log.warn("not banning us", new Exception()); return false; } boolean wasAlready = false; if (_log.shouldLog(Log.INFO)) _log.info("Banlist " + peer.toBase64() + ((transport != null) ? " on transport " + transport : ""), new Exception("Banlist cause: " + reason)); Entry e = new Entry(); e.expireOn = expireOn; e.cause = reason; e.causeCode = reasonCode; e.transports = null; if (transport != null) { e.transports = new ConcurrentHashSet<String>(2); e.transports.add(transport); } Entry old = _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; e.causeCode = old.causeCode; } if (e.transports != null) { if (old.transports != null) e.transports.addAll(old.transports); else { e.transports = null; e.cause = reason; e.causeCode = reasonCode; } } } _entries.put(peer, e); if (transport == null) { // we hate the peer on *any* transport _context.netDb().fail(peer); _context.tunnelManager().fail(peer); } //_context.tunnelManager().peerFailed(peer); //_context.messageRegistry().peerFailed(peer); if (!wasAlready) _context.messageHistory().banlist(peer, reason); return wasAlready; } public void unbanlistRouter(Hash peer) { unbanlistRouter(peer, true); } private void unbanlistRouter(Hash peer, boolean realUnbanlist) { unbanlistRouter(peer, realUnbanlist, null); } public void unbanlistRouter(Hash peer, String transport) { unbanlistRouter(peer, true, transport); } private void unbanlistRouter(Hash peer, boolean realUnbanlist, String transport) { if (peer == null) return; if (_log.shouldLog(Log.DEBUG)) _log.debug("unbanlist " + peer.toBase64() + (transport != null ? "/" + transport : "")); boolean fully = false; Entry e = _entries.remove(peer); if ( (e == null) || (e.transports == null) || (transport == null) || (e.transports.size() <= 1) ) { // fully unbanlisted fully = true; } else { e.transports.remove(transport); if (e.transports.isEmpty()) fully = true; else _entries.put(peer, e); } if (fully) { //if (realUnbanlist) { // PeerProfile prof = _context.profileOrganizer().getProfile(peer); // if (prof != null) // prof.unbanlist(); //} _context.messageHistory().unbanlist(peer); if (_log.shouldLog(Log.INFO) && e != null) _log.info("Unbanlisting router " + peer.toBase64() + (transport != null ? "/" + transport : "")); } } public boolean isBanlisted(Hash peer) { return isBanlisted(peer, null); } public boolean isBanlisted(Hash peer, String transport) { boolean rv = false; boolean unbanlist = false; Entry entry = _entries.get(peer); if (entry == null) { rv = false; } else if (entry.expireOn <= _context.clock().now()) { _entries.remove(peer); unbanlist = true; rv = false; } else if (entry.transports == null) { rv = true; } else { rv = entry.transports.contains(transport); } if (unbanlist) { //PeerProfile prof = _context.profileOrganizer().getProfile(peer); //if (prof != null) // prof.unbanlist(); _context.messageHistory().unbanlist(peer); if (_log.shouldLog(Log.INFO)) _log.info("Unbanlisting (expired) " + peer.toBase64()); } return rv; } public boolean isBanlistedForever(Hash peer) { Entry entry = _entries.get(peer); return entry != null && entry.expireOn > _context.clock().now() + 2*24*60*60*1000L; } /** @deprecated moved to router console */ @Deprecated public void renderStatusHTML(Writer out) throws IOException { } }