All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.i2p.router.Blocklist.orig Maven / Gradle / Ivy

package net.i2p.router;
/*
 * free (adj.): unencumbered; not under the control of others
 * Use at your own risk.
 * zzz 2008-06
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
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.TreeSet;

import net.i2p.app.ClientAppManager;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.GeoIP;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.update.UpdateManager;
import net.i2p.update.UpdateType;
import net.i2p.util.Addresses;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.LHMCache;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.SystemVersion;
import net.i2p.util.Translate;

/**
 * Manage blocking by IP address, in a manner similar to the Banlist,
 * which blocks by router hash.
 *
 * We also try to keep the two lists in sync: if a router at a given IP is
 * blocked, we will also banlist it "forever" (until the next reboot).
 *
 * While the reverse case (blocking the IP of a router banlisted forever)
 * is not automatic, the transports will call add() below to block the IP,
 * which allows the transports to terminate an inbound connection before
 * the router ident handshake.
 *
 * And the on-disk blocklist can also contain router hashes to be banlisted.
 *
 * So, this class maintains three separate lists:
 *
 *   1) The list of IP ranges, read in from a file at startup
 *   2) The list of hashes, read in from the same file
 *   3) A list of single IPs, initially empty, added to as needed
 *
* * Read in the IP blocklist from a file, store it in-memory as efficiently * as we can, and perform tests against it as requested. * * When queried for a peer that is blocklisted but isn't banlisted, * banlist it forever, then go back to the file to get the original * entry so we can add the reason to the banlist text. * * On-disk blocklist supports IPv4 only. * In-memory supports both IPv4 and IPv6. */ public class Blocklist { private final Log _log; private final RouterContext _context; private long _blocklist[]; private int _blocklistSize; private long _countryBlocklist[]; private int _countryBlocklistSize; private final Object _lock = new Object(); private Entry _wrapSave; private final Set _inProcess = new HashSet(4); private final File _blocklistFeedFile; private final boolean _haveIPv6; private boolean _started; private long _lastExpired = 0; // temp private final Map _peerBlocklist = new HashMap(4); private static final String PROP_BLOCKLIST_ENABLED = "router.blocklist.enable"; private static final String PROP_BLOCKLIST_DETAIL = "router.blocklist.detail"; private static final String PROP_BLOCKLIST_FILE = "router.blocklist.file"; private static final String PROP_BLOCKLIST_EXPIRE_INTERVAL = "router.blocklist.expireInterval"; public static final String BLOCKLIST_FILE_DEFAULT = "blocklist.txt"; private static final String BLOCKLIST_FEED_FILE = "docs/feed/blocklist/blocklist.txt"; /** @since 0.9.48 */ public static final String BLOCKLIST_COUNTRY_FILE = "blocklist-country.txt"; /** * Limits of transient (in-memory) blocklists. * Note that it's impossible to prevent clogging up * the tables by a determined attacker, esp. on IPv6 */ private static final int MAX_IPV4_SINGLES = SystemVersion.isSlow() ? 2048 : 8192; private static final int MAX_IPV6_SINGLES = SystemVersion.isSlow() ? 256 : 4096; private final Map _singleIPBlocklist = new LHMCache(MAX_IPV4_SINGLES); private final Map _singleIPv6Blocklist; private static final Object DUMMY = Integer.valueOf(0); /** * For Update Manager * @since 0.9.48 */ public static final String ID_FEED = "feed"; private static final String ID_SYSTEM = "system"; private static final String ID_LOCAL = "local"; private static final String ID_COUNTRY = "country"; private static final String ID_USER = "user"; public static final String ID_SYBIL = "sybil"; /** * Router MUST call startup() */ public Blocklist(RouterContext context) { _context = context; _log = context.logManager().getLog(Blocklist.class); _blocklistFeedFile = new File(context.getConfigDir(), BLOCKLIST_FEED_FILE); _haveIPv6 = TransportUtil.getIPv6Config(_context, "SSU") != TransportUtil.IPv6Config.IPV6_DISABLED && Addresses.isConnectedIPv6(); _singleIPv6Blocklist = _haveIPv6 ? new LHMCache(MAX_IPV6_SINGLES) : null; } /** only for testing with main() */ private Blocklist() { _context = null; _log = new Log(Blocklist.class); _blocklistFeedFile = new File(BLOCKLIST_FEED_FILE); _haveIPv6 = TransportUtil.getIPv6Config(_context, "SSU") != TransportUtil.IPv6Config.IPV6_DISABLED && Addresses.isConnectedIPv6(); _singleIPv6Blocklist = _haveIPv6 ? new LHMCache(MAX_IPV6_SINGLES) : null; } private int expireInterval(){ String expireIntervalValue = _context.getProperty(PROP_BLOCKLIST_EXPIRE_INTERVAL, "0"); try{ Integer expireIntervalInt = 0; if (expireIntervalValue.endsWith("s")) { expireIntervalValue = expireIntervalValue.substring(0, expireIntervalValue.length() - 1); expireIntervalInt = Integer.parseInt(expireIntervalValue) * 1000; }else if(expireIntervalValue.endsWith("m")){ expireIntervalValue = expireIntervalValue.substring(0, expireIntervalValue.length() - 1); expireIntervalInt = Integer.parseInt(expireIntervalValue) * 60000; }else if(expireIntervalValue.endsWith("h")){ expireIntervalValue = expireIntervalValue.substring(0, expireIntervalValue.length() - 1); expireIntervalInt = Integer.parseInt(expireIntervalValue) * 3600000; }else if (expireIntervalValue.endsWith("d")) { expireIntervalValue = expireIntervalValue.substring(0, expireIntervalValue.length() - 1); expireIntervalInt = Integer.parseInt(expireIntervalValue) * 86400000; }else{ expireIntervalInt = Integer.parseInt(expireIntervalValue); } if (expireIntervalInt < 0) expireIntervalInt = 0; return expireIntervalInt; }catch(NumberFormatException nfe){ if (_log.shouldLog(_log.ERROR)) _log.error("format error in "+PROP_BLOCKLIST_EXPIRE_INTERVAL, nfe); } // if we don't have a valid value in this field, return 0 which is the same as disabling it. return 0; } /** * Loads the following files in-order: * $I2P/blocklist.txt * ~/.i2p/blocklist.txt * ~/.i2p/docs/feed/blocklist/blocklist.txt * ~/.i2p/blocklist-countries.txt * File if specified with router.blocklist.file */ public synchronized void startup() { if (_started) return; _started = true; if (! _context.getBooleanPropertyDefaultTrue(PROP_BLOCKLIST_ENABLED)) return; List files = new ArrayList(5); // install dir File blFile = new File(_context.getBaseDir(), BLOCKLIST_FILE_DEFAULT); files.add(new BLFile(blFile, ID_SYSTEM)); // config dir if (!_context.getConfigDir().equals(_context.getBaseDir())) { blFile = new File(_context.getConfigDir(), BLOCKLIST_FILE_DEFAULT); files.add(new BLFile(blFile, ID_LOCAL)); } files.add(new BLFile(_blocklistFeedFile, ID_FEED)); if (_context.router().isHidden() || _context.getBooleanProperty(GeoIP.PROP_BLOCK_MY_COUNTRY)) { blFile = new File(_context.getConfigDir(), BLOCKLIST_COUNTRY_FILE); files.add(new BLFile(blFile, ID_COUNTRY)); } // user specified String file = _context.getProperty(PROP_BLOCKLIST_FILE); if (file != null && !file.equals(BLOCKLIST_FILE_DEFAULT)) { blFile = new File(file); if (!blFile.isAbsolute()) blFile = new File(_context.getConfigDir(), file); files.add(new BLFile(blFile, ID_USER)); } Job job = new ReadinJob(files); // Run immediately, so it's initialized before netdb. // As this is called by Router.runRouter() before job queue parallel operation, // this will block StartupJob, and will complete before netdb initialization. // If there is a huge blocklist, it will delay router startup, // but it's important to have this initialized before we read in the netdb. //job.getTiming().setStartAfter(_context.clock().now() + 30*1000); _context.jobQueue().addJob(job); if (expireInterval() > 0) { Job cleanupJob = new CleanupJob(); cleanupJob.getTiming().setStartAfter(_context.clock().now() + expireInterval()); _context.jobQueue().addJob(cleanupJob); } } /** * @since 0.9.48 */ private static class BLFile { public final File file; public final String id; public long version; public BLFile(File f, String s) { file = f; id = s; } } /** * Delay telling update manager until it's there * @since 0.9.48 */ private class VersionNotifier extends SimpleTimer2.TimedEvent { public final List blfs; public VersionNotifier(List bf) { super(_context.simpleTimer2(), 2*60*1000L); blfs = bf; } public void timeReached() { ClientAppManager cmgr = _context.clientAppManager(); if (cmgr != null) { UpdateManager umgr = (UpdateManager) cmgr.getRegisteredApp(UpdateManager.APP_NAME); if (umgr != null) { for (BLFile blf : blfs) { if (blf.version > 0) umgr.notifyInstalled(UpdateType.BLOCKLIST, blf.id, Long.toString(blf.version)); } } else { _log.warn("No update manager"); } } } } private class CleanupJob extends JobImpl { public CleanupJob() { super(_context); } public String getName(){ return "Expire blocklist at user-defined interval of " + expireInterval(); } public void runJob() { clear(); _lastExpired = System.currentTimeMillis(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Expiring blocklist entrys at" + _lastExpired); // schedule the next one super.requeue(expireInterval()); } } private void clear(){ synchronized(_singleIPBlocklist) { _singleIPBlocklist.clear(); } if (_singleIPv6Blocklist != null) { synchronized(_singleIPv6Blocklist) { _singleIPv6Blocklist.clear(); } } } private class ReadinJob extends JobImpl { private final List _files; /** * @param files not necessarily existing, but avoid dups */ public ReadinJob (List files) { super(_context); _files = files; } public String getName() { return "Read Blocklist"; } public void runJob() { synchronized (_lock) { _blocklist = allocate(_files); if (_blocklist == null) return; int ccount = process(); if (_blocklist == null) return; if (ccount <= 0) { disable(); return; } _blocklistSize = merge(_blocklist, ccount); // we're done with _peerBlocklist, but leave it // in case we need it for a later readin //_peerBlocklist = null; } // schedules itself new VersionNotifier(_files); } private int process() { int count = 0; try { for (BLFile blf : _files) { count = readBlocklistFile(blf, _blocklist, count); } } catch (OutOfMemoryError oom) { _log.log(Log.CRIT, "OOM processing the blocklist"); disable(); return 0; } for (Hash peer : _peerBlocklist.keySet()) { String reason; String comment = _peerBlocklist.get(peer); if (comment != null) reason = _x("Banned by router hash: {0}"); else reason = _x("Banned by router hash"); banlistRouter(peer, reason, comment); } _peerBlocklist.clear(); return count; } } private void banlistRouter(Hash peer, String reason, String comment) { if (expireInterval() > 0) _context.banlist().banlistRouter(peer, reason, comment, null, expireInterval()); else _context.banlist().banlistRouterForever(peer, reason, comment); } /** * The blocklist-country.txt file was created or updated. * Read it in. Not required normally, as the country file * is read by startup(). * @since 0.9.48 */ public synchronized void addCountryFile() { File blFile = new File(_context.getConfigDir(), BLOCKLIST_COUNTRY_FILE); BLFile blf = new BLFile(blFile, ID_COUNTRY); List c = Collections.singletonList(blf); long[] cb = allocate(c); if (cb == null) return; int count = readBlocklistFile(blf, cb, 0); if (count <= 0) return; ClientAppManager cmgr = _context.clientAppManager(); if (cmgr != null) { UpdateManager umgr = (UpdateManager) cmgr.getRegisteredApp(UpdateManager.APP_NAME); if (umgr != null) umgr.notifyInstalled(UpdateType.BLOCKLIST, ID_COUNTRY, Long.toString(blFile.lastModified())); } count = merge(cb, count); _countryBlocklistSize = count; _countryBlocklist = cb; } public void disable() { // hmm better block out any checks in process synchronized (_lock) { _blocklistSize = 0; _blocklist = null; } } /** * @return array or null on failure * @since 0.9.18 split out from readBlocklistFile() */ private long[] allocate(List files) { int maxSize = 0; for (BLFile blf : files) { maxSize += getSize(blf.file); } try { return new long[maxSize + files.size()]; // extra for wrapsave } catch (OutOfMemoryError oom) { _log.log(Log.CRIT, "OOM creating the blocklist"); return null; } } /** * Read in and parse the blocklist. * The blocklist need not be sorted, and may contain overlapping entries. * * Acceptable formats (IPV4 only): * #comment (# must be in column 1) * comment:IP-IP * comment:morecomments:IP-IP * IP-IP * (comments also allowed before any of the following) * IP/masklength * IP * hostname (DNS looked up at list readin time, not dynamically, so may not be much use) * 44-byte Base64 router hash * * Acceptable formats (IPV6 only): * comment:IPv6 (must replace : with ; e.g. abcd;1234;0;12;;ff) * IPv6 (must replace : with ; e.g. abcd;1234;0;12;;ff) * * No whitespace allowed after the last ':'. * * For further information and downloads: * http://www.bluetack.co.uk/forums/index.php?autocom=faq&CODE=02&qid=17 * http://blocklist.googlepages.com/ * http://www.cymru.com/Documents/bogon-list.html * * * Must call allocate() before and merge() after. * * @param blocklist out parameter, entries stored here * @param count current number of entries * @return new number of entries */ private int readBlocklistFile(BLFile blf, long[] blocklist, int count) { File blFile = blf.file; if (blFile == null || (!blFile.exists()) || blFile.length() <= 0) { if (_log.shouldLog(Log.WARN)) _log.warn("Blocklist file not found: " + blFile); return count; } long start = _context.clock().now(); int oldcount = count; int badcount = 0; int peercount = 0; int feedcount = 0; long ipcount = 0; final boolean isFeedFile = blFile.equals(_blocklistFeedFile); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader( new FileInputStream(blFile), "UTF-8")); String source = blFile.toString(); String buf = null; while ((buf = br.readLine()) != null) { Entry e = parse(buf, true); if (e == null) { badcount++; continue; } if (e.peer != null) { _peerBlocklist.put(e.peer, e.comment); peercount++; continue; } byte[] ip1 = e.ip1; if (ip1.length == 4) { //if (isFeedFile) { // // temporary // add(ip1, source); // feedcount++; //} else { byte[] ip2 = e.ip2; store(ip1, ip2, blocklist, count++); ipcount += 1 + toInt(ip2) - toInt(ip1); // includes dups, oh well //} } else { // IPv6 add(ip1, source); } } } catch (IOException ioe) { if (_log.shouldLog(Log.ERROR)) _log.error("Error reading the blocklist file", ioe); return count; } catch (OutOfMemoryError oom) { disable(); _log.log(Log.CRIT, "OOM reading the blocklist"); return 0; } finally { if (br != null) try { br.close(); } catch (IOException ioe) {} } if (_wrapSave != null) { // the extra record generated in parse() by a line that // wrapped around 128.0.0.0 store(_wrapSave.ip1, _wrapSave.ip2, blocklist, count++); ipcount += 1 + toInt(_wrapSave.ip2) - toInt(_wrapSave.ip1); _wrapSave = null; } int read = isFeedFile ? feedcount : (count - oldcount); // save to tell the update manager if (read > 0) blf.version = blFile.lastModified(); if (_log.shouldLog(Log.INFO)) { _log.info("Stats for " + blFile); _log.info("Removed " + badcount + " bad entries and comment lines"); _log.info("Read " + read + " valid entries from the blocklist " + blFile); //_log.info("Blocking " + (isFeedFile ? feedcount : ipcount) + " IPs and " + peercount + " hashes"); _log.info("Blocking " + ipcount + " IPs and " + peercount + " hashes"); _log.info("Blocklist processing finished, time: " + (_context.clock().now() - start)); } return count; } /** * @param count valid entries in blocklist before merge * @return count valid entries in blocklist after merge * @since 0.9.18 split out from readBlocklistFile() */ private int merge(long[] blocklist, int count) { long start = _context.clock().now(); // This is a standard signed sort, so the entries will be ordered // 128.0.0.0 ... 255.255.255.255 0.0.0.0 .... 127.255.255.255 // But that's ok. int removed = 0; try { Arrays.sort(blocklist, 0, count); removed = removeOverlap(blocklist, count); if (removed > 0) { // Sort again to remove the dups that were "zeroed" out as 127.255.255.255-255.255.255.255 Arrays.sort(blocklist, 0, count); // sorry, no realloc to save memory, don't want to blow up now } } catch (OutOfMemoryError oom) { disable(); _log.log(Log.CRIT, "OOM sorting the blocklist"); return 0; } int blocklistSize = count - removed; if (_log.shouldLog(Log.INFO)) { _log.info("Merged Stats:\n" + "Read " + count + " total entries from the blocklists\n" + "Merged " + removed + " overlapping entries\n" + "Result is " + blocklistSize + " entries\n" + "Blocklist processing finished, time: " + (_context.clock().now() - start)); } return blocklistSize; } /** * The result of parsing one line */ private static class Entry { final String comment; final byte ip1[]; final byte ip2[]; final Hash peer; public Entry(String c, Hash h, byte[] i1, byte[] i2) { comment = c; peer = h; ip1 = i1; ip2 = i2; } } /** * Parse one line, returning a temp data structure with the result */ private Entry parse(String buf, boolean shouldLog) { byte[] ip1; byte[] ip2; int start1 = 0; int end1 = buf.length(); if (end1 <= 0) return null; // blank //if (buf.charAt(end1 - 1) == '\r') { // DataHelper.readLine leaves the \r on there // buf.deleteCharAt(end1 - 1); // end1--; //} //if (end1 <= 0) // return null; // blank int start2 = -1; int mask = -1; String comment = null; int index = buf.indexOf('#'); if (index == 0) return null; // comment index = buf.lastIndexOf(':'); if (index >= 0) { comment = buf.substring(0, index); start1 = index + 1; } if (end1 - start1 == 44 && buf.substring(start1).indexOf('.') < 0) { byte b[] = Base64.decode(buf.substring(start1)); if (b != null) return new Entry(comment, Hash.create(b), null, null); } index = buf.indexOf('-', start1); if (index >= 0) { end1 = index; start2 = index + 1; } else { index = buf.indexOf('/', start1); if (index >= 0) { end1 = index; mask = index + 1; } } if (end1 - start1 <= 0) return null; // blank try { String sip = buf.substring(start1, end1); // IPv6 sip = sip.replace(';', ':'); InetAddress pi = InetAddress.getByName(sip); if (pi == null) return null; ip1 = pi.getAddress(); //if (ip1.length != 4) // throw new UnknownHostException(); if (start2 >= 0) { pi = InetAddress.getByName(buf.substring(start2)); if (pi == null) return null; ip2 = pi.getAddress(); if (ip2.length != 4) throw new UnknownHostException(); if ((ip1[0] & 0xff) < 0x80 && (ip2[0] & 0xff) >= 0x80) { if (_wrapSave == null) { // don't cross the boundary 127.255.255.255 - 128.0.0.0 // because we are sorting using signed arithmetic _wrapSave = new Entry(comment, null, new byte[] {(byte)0x80,0,0,0}, new byte[] {ip2[0], ip2[1], ip2[2], ip2[3]}); ip2 = new byte[] {127, (byte)0xff, (byte)0xff, (byte)0xff}; } else // We only save one entry crossing the boundary, throw the rest out throw new NumberFormatException(); } for (int i = 0; i < 4; i++) { if ((ip2[i] & 0xff) > (ip1[i] & 0xff)) break; if ((ip2[i] & 0xff) < (ip1[i] & 0xff)) throw new NumberFormatException(); // backwards } } else if (mask >= 0) { int m = Integer.parseInt(buf.substring(mask)); if (m < 3 || m > 32) throw new NumberFormatException(); ip2 = new byte[4]; // ick for (int i = 0; i < 4; i++) ip2[i] = ip1[i]; for (int i = 0; i < 32-m; i++) ip2[(31-i)/8] |= (0x01 << (i%8)); } else { ip2 = ip1; } } catch (UnknownHostException uhe) { if (shouldLog) _log.logAlways(Log.WARN, "Format error in the blocklist file: " + buf); return null; } catch (NumberFormatException nfe) { if (shouldLog) _log.logAlways(Log.WARN, "Format error in the blocklist file: " + buf); return null; } catch (IndexOutOfBoundsException ioobe) { if (shouldLog) _log.logAlways(Log.WARN, "Format error in the blocklist file: " + buf); return null; } return new Entry(comment, null, ip1, ip2); } /** * Read the file once just to see how many entries are in it, * so we can size our array. * This is i/o inefficient, but memory-efficient, which is what we want. */ private int getSize(File blFile) { if ( (!blFile.exists()) || (blFile.length() <= 0) ) return 0; int lines = 0; BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader( new FileInputStream(blFile), "ISO-8859-1")); String s; while ((s = br.readLine()) != null) { if (s.length() > 0 && !s.startsWith("#")) lines++; } } catch (IOException ioe) { if (_log.shouldLog(Log.WARN)) _log.warn("Error reading the blocklist file", ioe); return 0; } finally { if (br != null) try { br.close(); } catch (IOException ioe) {} } return lines; } /** * Merge and remove overlapping entries from a sorted list. * Returns number of removed entries. * Caller must re-sort if return code is > 0. */ private int removeOverlap(long blist[], int count) { if (count <= 0) return 0; int lines = 0; for (int i = 0; i < count - 1; ) { int removed = 0; int to = getTo(blist[i]); for (int next = i + 1; next < count; next++) { if (to < getFrom(blist[next])) break; if (_log.shouldInfo()) _log.info("Combining entries " + toStr(blist[i]) + " and " + toStr(blist[next])); int nextTo = getTo(blist[next]); if (nextTo > to) // else entry next is totally inside entry i store(getFrom(blist[i]), nextTo, blist, i); blist[next] = Long.MAX_VALUE; // to be removed with another sort lines++; removed++; } i += removed + 1; } return lines; } /** * Maintain a simple in-memory single-IP blocklist * This is used for new additions, NOT for the main list * of IP ranges read in from the file. * * @param ip IPv4 or IPv6 */ public void add(String ip) { if (!_haveIPv6 && ip.indexOf(':') >= 0) return; byte[] pib = Addresses.getIPOnly(ip); if (pib == null) return; add(pib, null); } /** * Maintain a simple in-memory single-IP blocklist * This is used for new additions, NOT for the main list * of IP ranges read in from the file. * * @param ip IPv4 or IPv6 * @param source for logging only, may be null * @since 0.9.57 */ public void add(String ip, String source) { if (!_haveIPv6 && ip.indexOf(':') >= 0) return; byte[] pib = Addresses.getIPOnly(ip); if (pib == null) return; add(pib, source); } /** * Maintain a simple in-memory single-IP blocklist * This is used for new additions, NOT for the main list * of IP ranges read in from the file. * * @param ip IPv4 or IPv6 */ public void add(byte ip[]) { add(ip, null); } /** * Maintain a simple in-memory single-IP blocklist * This is used for new additions, NOT for the main list * of IP ranges read in from the file. * * @param ip IPv4 or IPv6 * @param source for logging only, may be null * @since 0.9.57 */ public void add(byte ip[], String source) { boolean rv; if (ip.length == 4) { // don't ever block ourselves String us = _context.getProperty(UDPTransport.PROP_IP); if (us != null) { byte[] usb = Addresses.getIP(us); if (usb != null && DataHelper.eq(usb, ip)) { if (_log.shouldWarn()) _log.warn("Not adding our own IP " + us, new Exception()); return; } } rv = add(toInt(ip)); if (rv) _context.commSystem().removeExemption(Addresses.toString(ip)); } else if (ip.length == 16) { if (!_haveIPv6) return; // don't ever block ourselves String us = _context.getProperty(UDPTransport.PROP_IPV6); if (us != null) { byte[] usb = Addresses.getIP(us); if (usb != null && DataHelper.eq(usb, ip)) { if (_log.shouldWarn()) _log.warn("Not adding our own IP " + us, new Exception()); return; } } rv = add(new BigInteger(1, ip)); if (rv) _context.commSystem().removeExemption(Addresses.toCanonicalString(ip)); } else { return; } if (rv) { // lower log level at startup when initializing from blocklist files if (source == null && _log.shouldWarn()) _log.warn("Added: " + Addresses.toString(ip), new Exception("source")); else if (_log.shouldDebug()) _log.debug("Added: " + Addresses.toString(ip) + " source: " + source); } } /** * Remove from the in-memory single-IP blocklist. * This is only works to undo add()s, NOT for the main list * of IP ranges read in from the file. * * @param ip IPv4 or IPv6 * @since 0.9.28 */ public void remove(byte ip[]) { if (ip.length == 4) { remove(toInt(ip)); } else if (ip.length == 16) { if (!_haveIPv6) return; remove(new BigInteger(1, ip)); } } /** * @return true if it was NOT previously on the list */ private boolean add(int ip) { // save space, don't put in both if (isPermanentlyBlocklisted(ip)) return false; Integer iip = Integer.valueOf(ip); synchronized(_singleIPBlocklist) { return _singleIPBlocklist.put(iip, DUMMY) == null; } } /** * @since 0.9.28 */ private void remove(int ip) { Integer iip = Integer.valueOf(ip); synchronized(_singleIPBlocklist) { _singleIPBlocklist.remove(iip); } } private boolean isOnSingleList(int ip) { Integer iip = Integer.valueOf(ip); synchronized(_singleIPBlocklist) { return _singleIPBlocklist.get(iip) != null; } } /** * @param ip IPv6 non-negative * @return true if it was NOT previously on the list * @since IPv6 */ private boolean add(BigInteger ip) { if (_singleIPv6Blocklist != null) { synchronized(_singleIPv6Blocklist) { return _singleIPv6Blocklist.put(ip, DUMMY) == null; } } return false; } /** * @param ip IPv6 non-negative * @since 0.9.28 */ private void remove(BigInteger ip) { if (_singleIPv6Blocklist != null) { synchronized(_singleIPv6Blocklist) { _singleIPv6Blocklist.remove(ip); } } } /** * @param ip IPv6 non-negative * @since IPv6 */ private boolean isOnSingleList(BigInteger ip) { if (_singleIPv6Blocklist != null) { synchronized(_singleIPv6Blocklist) { return _singleIPv6Blocklist.get(ip) != null; } } return false; } /** * Will not contain duplicates. */ private List getAddresses(Hash peer) { RouterInfo pinfo = _context.netDb().lookupRouterInfoLocally(peer); if (pinfo == null) return Collections.emptyList(); return getAddresses(pinfo); } /** * Will not contain duplicates. * @since 0.9.29 */ private List getAddresses(RouterInfo pinfo) { List rv = new ArrayList(4); // for each peer address for (RouterAddress pa : pinfo.getAddresses()) { byte[] pib = pa.getIP(); if (pib == null) continue; if (!_haveIPv6 && pib.length == 16) continue; // O(n**2) boolean dup = false; for (int i = 0; i < rv.size(); i++) { if (DataHelper.eq(rv.get(i), pib)) { dup = true; break; } } if (!dup) rv.add(pib); } return rv; } /** * Does the peer's IP address appear in the blocklist? * If so, and it isn't banlisted, banlist it forever... * or, if the user configured an override, ban it for the * override period. * @since 0.9.29 */ public boolean isBlocklisted(Hash peer) { List ips = getAddresses(peer); if (ips.isEmpty()) return false; for (byte[] ip : ips) { if (isBlocklisted(ip)) { if (! _context.banlist().isBanlisted(peer)) // nice knowing you... banlist(peer, ip); return true; } } return false; } /** * Does the peer's IP address appear in the blocklist? * If so, and it isn't banlisted, banlist it forever... * or, if the user configured an override, ban it for the * override period. * @since 0.9.29 */ public boolean isBlocklisted(RouterInfo pinfo) { List ips = getAddresses(pinfo); if (ips.isEmpty()) return false; for (byte[] ip : ips) { if (isBlocklisted(ip)) { Hash peer = pinfo.getHash(); if (! _context.banlist().isBanlisted(peer)) // nice knowing you... banlist(peer, ip); return true; } } return false; } /** * calling this externally won't banlist the peer, this is just an IP check * * @param ip IPv4 or IPv6 */ public boolean isBlocklisted(String ip) { if (!_haveIPv6 && ip.indexOf(':') >= 0) return false; byte[] pib = Addresses.getIPOnly(ip); if (pib == null) return false; return isBlocklisted(pib); } /** * calling this externally won't banlist the peer, this is just an IP check * * @param ip IPv4 or IPv6 */ public boolean isBlocklisted(byte ip[]) { if (ip.length == 4) return isBlocklisted(toInt(ip)); if (ip.length == 16) { if (!_haveIPv6) return false; return isOnSingleList(new BigInteger(1, ip)); } return false; } /** * First check the single-IP list. * Then do a * binary search through the in-memory range list which * is a sorted array of longs. * The array is sorted in signed order, but we don't care. * Each long is ((from << 32) | to) */ private boolean isBlocklisted(int ip) { if (isOnSingleList(ip)) return true; if (_countryBlocklist != null) { if (isPermanentlyBlocklisted(ip, _countryBlocklist, _countryBlocklistSize)) return true; } return isPermanentlyBlocklisted(ip); } /** * Do a binary search through the in-memory range list which * is a sorted array of longs. * The array is sorted in signed order, but we don't care. * Each long is ((from << 32) | to) * * Public for console only, not a public API * * @since 0.9.45 split out from above, public since 0.9.48 for console */ public boolean isPermanentlyBlocklisted(int ip) { return isPermanentlyBlocklisted(ip, _blocklist, _blocklistSize); } /** * Do a binary search through the in-memory range list which * is a sorted array of longs. * The array is sorted in signed order, but we don't care. * Each long is ((from << 32) | to) * * @since 0.9.48 split out from above */ private static boolean isPermanentlyBlocklisted(int ip, long[] blocklist, int blocklistSize) { int hi = blocklistSize - 1; if (hi <= 0) return false; int lo = 0; int cur = hi / 2; while (!match(ip, blocklist[cur])) { if (isHigher(ip, blocklist[cur])) lo = cur; else hi = cur; // make sure we get the last one if (hi - lo <= 1) { if (lo == cur) cur = hi; else cur = lo; break; } else { cur = lo + ((hi - lo) / 2); } } return match(ip, blocklist[cur]); } /* // Is the IP included in the entry _blocklist[cur] ? private boolean match(int ip, int cur) { return match(ip, _blocklist[cur]); } */ // Is the IP included in the compressed entry? private static boolean match(int ip, long entry) { if (getFrom(entry) > ip) return false; return (ip <= getTo(entry)); } // Is the IP higher than the entry _blocklist[cur] ? private static boolean isHigher(int ip, long entry) { return ip > getFrom(entry); } // methods to get and store the from/to values in the array /** * Public for console only, not a public API * @since public since 0.9.48 */ public static int getFrom(long entry) { return (int) ((entry >> 32) & 0xffffffff); } /** * Public for console only, not a public API * @since public since 0.9.48 */ public static int getTo(long entry) { return (int) (entry & 0xffffffff); } /** * The in-memory blocklist is an array of longs, with the format * ((from IP) << 32) | (to IP) * The XOR is so the signed sort is in normal (unsigned) order. * * So the size is (cough) almost 2MB for the 240,000 line splist.txt. * */ private static long toEntry(byte ip1[], byte ip2[]) { long entry = 0; for (int i = 0; i < 4; i++) entry |= ((long) (ip2[i] & 0xff)) << ((3-i)*8); for (int i = 0; i < 4; i++) entry |= ((long) (ip1[i] & 0xff)) << (32 + ((3-i)*8)); return entry; } /** * IPv4 only */ private static void store(byte ip1[], byte ip2[], long[] blocklist, int idx) { blocklist[idx] = toEntry(ip1, ip2); } private static void store(int ip1, int ip2, long[] blocklist, int idx) { long entry = ((long) ip1) << 32; entry |= ((long)ip2) & 0xffffffff; blocklist[idx] = entry; } private static int toInt(byte ip[]) { int rv = 0; for (int i = 0; i < 4; i++) rv |= (ip[i] & 0xff) << ((3-i)*8); return rv; } private static String toStr(long entry) { StringBuilder buf = new StringBuilder(32); for (int i = 7; i >= 0; i--) { buf.append((entry >> (8*i)) & 0xff); if (i == 4) buf.append('-'); else if (i > 0) buf.append('.'); } return buf.toString(); } /** * Public for console only, not a public API * @since public since 0.9.48 */ public static String toStr(int ip) { StringBuilder buf = new StringBuilder(16); for (int i = 3; i >= 0; i--) { buf.append((ip >> (8*i)) & 0xff); if (i > 0) buf.append('.'); } return buf.toString(); } /** * We don't keep the comment field in-memory, * so we have to go back out to the file to find it. * * Put this in a job because we're looking for the * actual line in the blocklist file, this could take a while. * */ private void banlist(Hash peer, byte[] ip) { // Don't bother unless we have IPv6 if (!_haveIPv6 && ip.length == 16) return; // Temporary reason, until the job finishes String reason = _x("IP banned by blocklist.txt entry {0}"); String sip = Addresses.toString(ip); if ("127.0.0.1".equals(sip) || "0:0:0:0:0:0:0:1".equals(sip) || sip.startsWith("192.168.")) { // i2pd bug, possibly at startup, don't ban forever _context.banlist().banlistRouter(peer, reason, sip, null, _context.clock().now() + Banlist.BANLIST_DURATION_LOCALHOST); return; } banlistRouter(peer, reason, sip); if (! _context.getBooleanPropertyDefaultTrue(PROP_BLOCKLIST_DETAIL)) return; boolean shouldRunJob; int number; synchronized (_inProcess) { number = _inProcess.size(); shouldRunJob = _inProcess.add(peer); } if (!shouldRunJob) return; // get the IPs now because it won't be in the netdb by the time the job runs Job job = new BanlistJob(peer, getAddresses(peer)); if (number > 0) job.getTiming().setStartAfter(_context.clock().now() + (30*1000l * number)); _context.jobQueue().addJob(job); } private class BanlistJob extends JobImpl { private final Hash _peer; private final List _ips; public BanlistJob (Hash p, List ips) { super(_context); _peer = p; _ips = ips; } public String getName() { return "Ban Peer by IP"; } public void runJob() { banlistRouter(_peer, _ips, expireInterval()); synchronized (_inProcess) { _inProcess.remove(_peer); } } } /** * Look up the original record so we can record the reason in the banlist. * That's the only reason to do this. * Only synchronize to cut down on the I/O load. * Additional jobs can wait. * Although could this clog up the job queue runners? Yes. * So we also stagger these jobs. * */ private void banlistRouter( Hash peer, String reason, String reasonCode, long duration) { if (duration > 0) _context.banlist().banlistRouter(peer, reason, reasonCode, null, System.currentTimeMillis()+expireInterval()); else _context.banlist().banlistRouterForever(peer, reason, reasonCode); } private synchronized void banlistRouter(Hash peer, List ips, long duration) { // This only checks one file for now, pick the best one // user specified File blFile = null; String file = _context.getProperty(PROP_BLOCKLIST_FILE); if (file != null) { blFile = new File(file); if (!blFile.isAbsolute()) blFile = new File(_context.getConfigDir(), file); if (!blFile.exists()) blFile = null; } // install dir if (blFile == null) blFile = new File(_context.getBaseDir(), BLOCKLIST_FILE_DEFAULT); if ((!blFile.exists()) || blFile.length() <= 0) { // just ban it and be done if (_log.shouldLog(Log.WARN)) _log.warn("Banlisting " + peer); banlistRouter(peer, "Banned", "Banned", expireInterval()); return; } // look through the file for each address to find which one was the cause for (Iterator iter = ips.iterator(); iter.hasNext(); ) { byte ip[] = iter.next(); int ipint = toInt(ip); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader( new FileInputStream(blFile), "UTF-8")); String buf = null; // assume the file is unsorted, so go through the whole thing while ((buf = br.readLine()) != null) { Entry e = parse(buf, false); if (e == null || e.peer != null) { continue; } if (match(ipint, toEntry(e.ip1, e.ip2))) { try { br.close(); } catch (IOException ioe) {} String reason = _x("IP banned by blocklist.txt entry {0}"); // only one translate parameter for now //for (int i = 0; i < 4; i++) { // reason = reason + (ip[i] & 0xff); // if (i != 3) // reason = reason + '.'; //} //reason = reason + " banned by " + BLOCKLIST_FILE_DEFAULT + " entry \"" + buf + "\""; if (_log.shouldLog(Log.WARN)) _log.warn("Banlisting " + peer + " " + reason); banlistRouter(peer, reason, buf.toString(), expireInterval()); return; } } } catch (IOException ioe) { if (_log.shouldLog(Log.WARN)) _log.warn("Error reading the blocklist file", ioe); } finally { if (br != null) try { br.close(); } catch (IOException ioe) {} } } // We already banlisted in banlist(peer), that's good enough } /** * Single IPs blocked until restart. Unsorted. * * Public for console only, not a public API * As of 0.9.57, will not contain IPs permanently banned, * except for ones banned permanently after being added to the transient list. * * @return a copy, unsorted * @since 0.9.48 */ public List getTransientIPv4Blocks() { synchronized(_singleIPBlocklist) { return new ArrayList(_singleIPBlocklist.keySet()); } } /** * Single IPs blocked until restart. Unsorted. * * Public for console only, not a public API * * @return a copy, unsorted * @since 0.9.48 */ public List getTransientIPv6Blocks() { if (!_haveIPv6) return Collections.emptyList(); if (_singleIPv6Blocklist != null) { synchronized(_singleIPv6Blocklist) { return new ArrayList(_singleIPv6Blocklist.keySet()); } } return Collections.emptyList(); } /** * IP ranges blocked until restart. Sorted, * but as signed longs, so 128-255 are first * * Public for console only, not a public API * * @param max maximum entries to return * @return a copy, sorted * @since 0.9.48 */ public synchronized long[] getPermanentBlocks(int max) { long[] rv; if (_blocklistSize <= max) { rv = new long[_blocklistSize]; System.arraycopy(_blocklist, 0, rv, 0, _blocklistSize); } else { // skip ahead to the positive entries int i = 0; for (; i < _blocklistSize; i++) { int from = Blocklist.getFrom(_blocklist[i]); if (from >= 0) break; } int sz = Math.min(_blocklistSize - i, max); rv = new long[sz]; System.arraycopy(_blocklist, i, rv, 0, sz); } return rv; } /** * Size of permanent blocklist * * Public for console only, not a public API * * @since 0.9.48 */ public synchronized int getBlocklistSize() { return _blocklistSize; } /** * Does nothing, moved to console ConfigPeerHelper * * @deprecated */ @Deprecated public void renderStatusHTML(Writer out) throws IOException { } /** * Mark a string for extraction by xgettext and translation. * Use this only in static initializers. * It does not translate! * @return s */ private static final String _x(String s) { return s; } /**** public static void main(String args[]) throws Exception { Blocklist b = new Blocklist(new Router().getContext()); if (args != null && args.length == 1) { File f = new File(args[0]); b.allocate(Collections.singletonList(f)); int count = b.readBlocklistFile(f, 0); b.merge(count); Writer w = new java.io.OutputStreamWriter(System.out); b.renderStatusHTML(w); } System.out.println("Saved " + b._blocklistSize + " records"); String tests[] = {"0.0.0.0", "0.0.0.1", "0.0.0.2", "0.0.0.255", "1.0.0.0", "3.3.3.3", "77.1.2.3", "127.0.0.0", "127.127.127.127", "128.0.0.0", "129.1.2.3", "255.255.255.254", "255.255.255.255"}; for (int i = 0; i < tests.length; i++) { System.out.println("Testing " + tests[i] + " returns " + b.isBlocklisted(tests[i])); } } ****/ }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy