diff --git a/checklist.txt b/checklist.txt index 75187a0c525545e101bee68f06250bc290316cf9..591cba775dc4811dbe099526f2048ef494c6a869 100644 --- a/checklist.txt +++ b/checklist.txt @@ -67,6 +67,7 @@ Website files to change: index.html index_de.html hosts.txt (copy from mtn) + release-x.y.z.html (new) Sync with mtn.i2p2.i2p Copy news.xml to subscription location diff --git a/history.txt b/history.txt index 3e39509d033d09679862ba7c895bfaed391d80d0..7a75d0f1377bc64100f5cc441b502991db6c403a 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,12 @@ +2008-07-30 zzz + * Blocklists: + - New, disabled by default, except for blocking of + forever-shitlisted peers. See source for instructions + and file format. + * Transport - Reject peers from inbound connections: + - Check IP against blocklist + - Check router hash against forever-shitlist, then block IP + 2008-07-16 zzz * configpeer.jsp: New * i2psnark: Open completed files read-only the first time diff --git a/news.xml b/news.xml index 17199a86889969365868cfdb21b72a6306bb8b4c..e9efe9c7f414dcad271ac4816ee3585031595b72 100644 --- a/news.xml +++ b/news.xml @@ -24,16 +24,6 @@ for an adversary to gather meaningful statistics for a predecessor attack. Improvements to applications like I2PSnark and the Router Console are also introduced. </p> -<p> -• -2008-04-20: <b><a href="http://trac.i2p2.i2p/">We are now using Trac as bugtracker</a></b> -</p> - -<p> -• -2008-02-05: <b><a href="http://www.i2p2.i2p/upgrade-0.6.1.30.html">Upgrading from 0.6.1.30 and Earlier Releases</a></b> -</p> - <!-- • 2007-04-10: diff --git a/router/java/src/net/i2p/router/Blocklist.java b/router/java/src/net/i2p/router/Blocklist.java new file mode 100644 index 0000000000000000000000000000000000000000..73682ec6096b166b4ce0ff01bcef51fd5758d7cc --- /dev/null +++ b/router/java/src/net/i2p/router/Blocklist.java @@ -0,0 +1,749 @@ +package net.i2p.router; +/* + * free (adj.): unencumbered; not under the control of others + * Use at your own risk. + * zzz 2008-06 + */ + +import java.io.IOException; +import java.io.File; +import java.io.FileInputStream; +import java.io.Writer; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.RouterAddress; +import net.i2p.data.RouterInfo; +import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import net.i2p.util.HexDump; +import net.i2p.util.Log; + +/** + * Manage blocking by IP address, in a manner similar to the Shitlist, + * 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 shitlist it "forever" (until the next reboot). + * + * While the reverse case (blocking the IP of a router shitlisted 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 shitlisted. + * + * 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 shitlisted, + * shitlist it forever, then go back to the file to get the original + * entry so we can add the reason to the shitlist text. + * + */ +public class Blocklist { + private Log _log; + private RouterContext _context; + private long _blocklist[]; + private int _blocklistSize; + private Object _lock; + private Entry _wrapSave; + private Set _inProcess; + private Map _peerBlocklist; + private Set _singleIPBlocklist; + + public Blocklist(RouterContext context) { + _context = context; + _log = context.logManager().getLog(Blocklist.class); + _blocklist = null; + _blocklistSize = 0; + _lock = new Object(); + _wrapSave = null; + _inProcess = new HashSet(0); + _peerBlocklist = new HashMap(0); + _singleIPBlocklist = new HashSet(0); + } + + public Blocklist() { + _log = new Log(Blocklist.class); + _blocklist = null; + _blocklistSize = 0; + } + + static final String PROP_BLOCKLIST_ENABLED = "router.blocklist.enable"; + static final String PROP_BLOCKLIST_DETAIL = "router.blocklist.detail"; + static final String PROP_BLOCKLIST_FILE = "router.blocklist.file"; + static final String BLOCKLIST_FILE_DEFAULT = "blocklist.txt"; + + public void startup() { + String enabled = _context.getProperty(PROP_BLOCKLIST_ENABLED, "false"); + if (! "true".equals(enabled)) + return; + String file = _context.getProperty(PROP_BLOCKLIST_FILE, BLOCKLIST_FILE_DEFAULT); + // Maybe someday we'll read in multiple files and merge them + // StringTokenizer tok = new StringTokenizer(file, " ,\r\n"); + // while (tok.hasMoreTokens()) + // readBlocklistFile(tok.nextToken()); + Job job = new ReadinJob(file); + job.getTiming().setStartAfter(_context.clock().now() + 2*60*1000); + _context.jobQueue().addJob(job); + } + + private class ReadinJob extends JobImpl { + private String _file; + public ReadinJob (String f) { + super(_context); + _file = f; + } + public String getName() { return "Read Blocklist"; } + public void runJob() { + synchronized (_lock) { + try { + readBlocklistFile(_file); + } catch (OutOfMemoryError oom) { + _log.log(Log.CRIT, "OOM processing the blocklist"); + disable(); + return; + } + } + if (_blocklistSize <= 0) + return; + FloodfillNetworkDatabaseFacade fndf = (FloodfillNetworkDatabaseFacade) _context.netDb(); + int count = 0; + for (Iterator iter = fndf.getKnownRouterData().iterator(); iter.hasNext(); ) { + RouterInfo ri = (RouterInfo) iter.next(); + Hash peer = ri.getIdentity().getHash(); + if (isBlocklisted(peer)) + count++; + } + if (count > 0 && _log.shouldLog(Log.ERROR)) + _log.error("Blocklisted " + count + " routers in the netDb."); + for (Iterator iter = _peerBlocklist.keySet().iterator(); iter.hasNext(); ) { + Hash peer = (Hash) iter.next(); + String reason = "Blocklisted by router hash"; + String comment = (String) _peerBlocklist.get(peer); + if (comment != null) + reason = reason + ": " + comment; + _context.shitlist().shitlistRouterForever(peer, reason); + } + _peerBlocklist = null; + } + } + + public void disable() { + // hmm better block out any checks in process + synchronized (_lock) { + _blocklistSize = 0; + _blocklist = 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 + * + * 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 + */ + private void readBlocklistFile(String file) { + File BLFile = new File(file); + if (BLFile == null || (!BLFile.exists()) || BLFile.length() <= 0) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Blocklist file not found: " + file); + return; + } + long start = _context.clock().now(); + int maxSize = getSize(BLFile); + try { + _blocklist = new long[maxSize + 1]; // extra for wrapsave + } catch (OutOfMemoryError oom) { + _log.log(Log.CRIT, "OOM creating the blocklist"); + return; + } + int count = 0; + int badcount = 0; + int peercount = 0; + long ipcount = 0; + FileInputStream in = null; + try { + in = new FileInputStream(BLFile); + StringBuffer buf = new StringBuffer(128); + while (DataHelper.readLine(in, buf) && count < maxSize) { + Entry e = parse(buf, true); + buf.setLength(0); + if (e == null) { + badcount++; + continue; + } + if (e.peer != null) { + _peerBlocklist.put(e.peer, e.comment); + peercount++; + continue; + } + byte[] ip1 = e.ip1; + byte[] ip2 = e.ip2; + + store(ip1, ip2, count++); + ipcount += 1 + toInt(ip2) - toInt(ip1); // includes dups, oh well + } + } catch (IOException ioe) { + _blocklist = null; + if (_log.shouldLog(Log.ERROR)) + _log.error("Error reading the BLFile", ioe); + return; + } catch (OutOfMemoryError oom) { + _blocklist = null; + _log.log(Log.CRIT, "OOM reading the blocklist"); + return; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + + if (_wrapSave != null) { + store(_wrapSave.ip1, _wrapSave.ip2, count++); + ipcount += 1 + toInt(_wrapSave.ip2) - toInt(_wrapSave.ip1); + } + + // 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) { + _blocklist = null; + _log.log(Log.CRIT, "OOM sorting the blocklist"); + return; + } + _blocklistSize = count - removed; + if (_log.shouldLog(Log.ERROR)) { + _log.error("Removed " + badcount + " bad entries and comment lines"); + _log.error("Read " + count + " valid entries from the blocklist " + BLFile); + _log.error("Merged " + removed + " overlapping entries"); + _log.error("Result is " + _blocklistSize + " entries"); + _log.error("Blocking " + ipcount + " IPs and " + peercount + " hashes"); + _log.error("Blocklist processing finished, time: " + (_context.clock().now() - start)); + } + } + + private class Entry { + String comment; + byte ip1[]; + byte ip2[]; + Hash peer; + + public Entry(String c, Hash h, byte[] i1, byte[] i2) { + comment = c; + peer = h; + ip1 = i1; + ip2 = i2; + } + } + + private Entry parse(StringBuffer buf, boolean bitch) { + byte[] ip1; + byte[] ip2; + int start1 = 0; + int end1 = buf.length(); + 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, new Hash(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; + } + } + try { + InetAddress pi = InetAddress.getByName(buf.substring(start1, end1)); + 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 (bitch && _log.shouldLog(Log.ERROR)) + _log.error("Format error in the blocklist file: " + buf); + return null; + } catch (NumberFormatException nfe) { + if (bitch && _log.shouldLog(Log.ERROR)) + _log.error("Format error in the blocklist file: " + buf); + return null; + } catch (IndexOutOfBoundsException ioobe) { + if (bitch && _log.shouldLog(Log.ERROR)) + _log.error("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; + FileInputStream in = null; + try { + in = new FileInputStream(BLFile); + StringBuffer buf = new StringBuffer(128); + while (DataHelper.readLine(in, buf)) { + lines++; + buf.setLength(0); + } + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Error reading the BLFile", ioe); + return 0; + } finally { + if (in != null) try { in.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.shouldLog(Log.WARN)) + _log.warn("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, 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. + public void add(String ip) { + InetAddress pi; + try { + pi = InetAddress.getByName(ip); + } catch (UnknownHostException uhe) { + return; + } + if (pi == null) return; + byte[] pib = pi.getAddress(); + add(pib); + } + + public void add(byte ip[]) { + if (ip.length != 4) + return; + if (add(toInt(ip))) + if (_log.shouldLog(Log.ERROR)) + _log.error("Adding IP to blocklist: " + ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]); + } + + private boolean add(int ip) { + synchronized(_singleIPBlocklist) { + return _singleIPBlocklist.add(new Integer(ip)); + } + } + + private boolean isOnSingleList(int ip) { + synchronized(_singleIPBlocklist) { + return _singleIPBlocklist.contains(new Integer(ip)); + } + } + + /** + * this tries to not return duplicates + * but I suppose it could. + */ + public List getAddresses(Hash peer) { + List rv = new ArrayList(1); + RouterInfo pinfo = _context.netDb().lookupRouterInfoLocally(peer); + if (pinfo == null) return rv; + Set paddr = pinfo.getAddresses(); + if (paddr == null || paddr.size() == 0) + return rv; + String oldphost = null; + List pladdr = new ArrayList(paddr); + // for each peer address + for (int j = 0; j < paddr.size(); j++) { + RouterAddress pa = (RouterAddress) pladdr.get(j); + if (pa == null) continue; + Properties pprops = pa.getOptions(); + if (pprops == null) continue; + String phost = pprops.getProperty("host"); + if (phost == null) continue; + if (oldphost != null && oldphost.equals(phost)) continue; + oldphost = phost; + InetAddress pi; + try { + pi = InetAddress.getByName(phost); + } catch (UnknownHostException uhe) { + continue; + } + if (pi == null) continue; + byte[] pib = pi.getAddress(); + rv.add(pib); + } + return rv; + } + + /** + * Does the peer's IP address appear in the blocklist? + * If so, and it isn't shitlisted, shitlist it forever... + */ + public boolean isBlocklisted(Hash peer) { + List ips = getAddresses(peer); + for (Iterator iter = ips.iterator(); iter.hasNext(); ) { + byte ip[] = (byte[]) iter.next(); + if (isBlocklisted(ip)) { + if (! _context.shitlist().isShitlisted(peer)) + // nice knowing you... + shitlist(peer); + return true; + } + } + return false; + } + + // calling this externally won't shitlist the peer, this is just an IP check + public boolean isBlocklisted(String ip) { + InetAddress pi; + try { + pi = InetAddress.getByName(ip); + } catch (UnknownHostException uhe) { + return false; + } + if (pi == null) return false; + byte[] pib = pi.getAddress(); + return isBlocklisted(pib); + } + + // calling this externally won't shitlist the peer, this is just an IP check + public boolean isBlocklisted(byte ip[]) { + if (ip.length != 4) + return false; + return isBlocklisted(toInt(ip)); + } + + /** + * 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; + int hi = _blocklistSize - 1; + if (hi <= 0) + return false; + int lo = 0; + int cur = hi / 2; + + while (!match(ip, cur)) { + if (isHigher(ip, 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, 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 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 boolean isHigher(int ip, int cur) { + return ip > getFrom(_blocklist[cur]); + } + + // methods to get and store the from/to values in the array + + private int getFrom(long entry) { + return (int) ((entry >> 32) & 0xffffffff); + } + + private 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 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; + } + + private void store(byte ip1[], byte ip2[], int idx) { + _blocklist[idx] = toEntry(ip1, ip2); + } + + private void store(int ip1, int ip2, int idx) { + long entry = ((long) ip1) << 32; + entry |= ip2; + _blocklist[idx] = entry; + } + + private int toInt(byte ip[]) { + int rv = 0; + for (int i = 0; i < 4; i++) + rv |= (ip[i] & 0xff) << ((3-i)*8); + return rv; + } + + private String toStr(long entry) { + StringBuffer buf = new StringBuffer(128); + 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(); + } + + /** + * 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. + * + */ + public void shitlist(Hash peer) { + // Temporary reason, until the job finishes + _context.shitlist().shitlistRouterForever(peer, "IP Blocklisted"); + if (! "true".equals( _context.getProperty(PROP_BLOCKLIST_DETAIL, "true"))) + return; + boolean shouldRunJob; + int number; + synchronized (_inProcess) { + number = _inProcess.size(); + shouldRunJob = _inProcess.add(peer); + } + if (!shouldRunJob) + return; + Job job = new ShitlistJob(peer); + if (number > 0) + job.getTiming().setStartAfter(_context.clock().now() + (number * 30*1000)); + _context.jobQueue().addJob(job); + } + + private class ShitlistJob extends JobImpl { + private Hash _peer; + public ShitlistJob (Hash p) { + super(_context); + _peer = p; + } + public String getName() { return "Shitlist Peer Forever"; } + public void runJob() { + shitlistForever(_peer); + synchronized (_inProcess) { + _inProcess.remove(_peer); + } + } + } + + /** + * Look up the original record so we can record the reason in the shitlist. + * 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 synchronized void shitlistForever(Hash peer) { + String file = _context.getProperty(PROP_BLOCKLIST_FILE, BLOCKLIST_FILE_DEFAULT); + File BLFile = new File(file); + if (BLFile == null || (!BLFile.exists()) || BLFile.length() <= 0) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Blocklist file not found: " + file); + return; + } + + // look through the file for each address to find which one was the cause + List ips = getAddresses(peer); + for (Iterator iter = ips.iterator(); iter.hasNext(); ) { + byte ip[] = (byte[]) iter.next(); + int ipint = toInt(ip); + FileInputStream in = null; + try { + in = new FileInputStream(BLFile); + StringBuffer buf = new StringBuffer(128); + // assume the file is unsorted, so go through the whole thing + while (DataHelper.readLine(in, buf)) { + Entry e = parse(buf, false); + if (e == null || e.peer != null) { + buf.setLength(0); + continue; + } + if (match(ipint, toEntry(e.ip1, e.ip2))) { + try { in.close(); } catch (IOException ioe) {} + String reason = "IP "; + for (int i = 0; i < 4; i++) { + reason = reason + (ip[i] & 0xff); + if (i != 3) + reason = reason + '.'; + } + reason = reason + " blocklisted by entry \"" + buf + "\""; + if (_log.shouldLog(Log.ERROR)) + _log.error("Shitlisting " + peer + " " + reason); + _context.shitlist().shitlistRouterForever(peer, reason); + return; + } + buf.setLength(0); + } + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Error reading the BLFile", ioe); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + // We already shitlisted in shitlist(peer), that's good enough + } + + public static void main(String args[]) { + Blocklist b = new Blocklist(); + if ( (args != null) && (args.length == 1) ) + b.readBlocklistFile(args[0]); + 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])); + } + } +} diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 3b687098fdb84795ebd604c10687a03540be85d5..827e0c457e6ef9b474e5a32148aafef42848902a 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -259,6 +259,7 @@ public class Router { warmupCrypto(); _sessionKeyPersistenceHelper.startup(); //_context.adminManager().startup(); + _context.blocklist().startup(); // let the timestamper get us sync'ed long before = System.currentTimeMillis(); @@ -380,8 +381,8 @@ public class Router { int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(), _context.bandwidthLimiter().getOutboundKBytesPerSecond()); bwLim = (int)(((float)bwLim) * getSharePercentage()); - if (_log.shouldLog(Log.WARN)) - _log.warn("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps")); + if (_log.shouldLog(Log.INFO)) + _log.info("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps")); if (bwLim < 12) { ri.addCapability(CAPABILITY_BW12); diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 195db00d0d468786170b914feea3b4c2f9d1c922..8b34f636417fecf9f5d43345fb30cca459729e65 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -56,6 +56,7 @@ public class RouterContext extends I2PAppContext { private TunnelDispatcher _tunnelDispatcher; private StatisticsManager _statPublisher; private Shitlist _shitlist; + private Blocklist _blocklist; private MessageValidator _messageValidator; private MessageStateMonitor _messageStateMonitor; private RouterThrottle _throttle; @@ -126,6 +127,7 @@ public class RouterContext extends I2PAppContext { _tunnelDispatcher = new TunnelDispatcher(this); _statPublisher = new StatisticsManager(this); _shitlist = new Shitlist(this); + _blocklist = new Blocklist(this); _messageValidator = new MessageValidator(this); //_throttle = new RouterThrottleImpl(this); _throttle = new RouterDoSThrottle(this); @@ -249,6 +251,7 @@ public class RouterContext extends I2PAppContext { * who does this peer hate? */ public Shitlist shitlist() { return _shitlist; } + public Blocklist blocklist() { return _blocklist; } /** * The router keeps track of messages it receives to prevent duplicates, as * well as other criteria for "validity". @@ -346,4 +349,4 @@ public class RouterContext extends I2PAppContext { } } -} \ No newline at end of file +} diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 6900613bee42561555e8b662e64b2ace753bda7d..2e107107a7603a6b96b67d6a66832259022dbf79 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = "0.6.2"; - public final static long BUILD = 9; + public final static long BUILD = 10; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index 1ecc078d11de7f5ada4d6753daadf1f0651f988f..d18cc1ad3aea1d64dbedd0ad58f84ab86f654ae6 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -546,6 +546,15 @@ public class EstablishState { Signature sig = new Signature(s); _verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey()); if (_verified) { + if (_context.shitlist().isShitlistedForever(alice.calculateHash())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Dropping inbound connection from permanently shitlisted peer: " + alice.calculateHash().toBase64()); + // So next time we will not accept the con from this IP, + // rather than doing the whole handshake + _context.blocklist().add(_con.getChannel().socket().getInetAddress().getAddress()); + fail("Peer is shitlisted forever: " + alice.calculateHash().toBase64()); + return; + } if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "verification successful for " + _con); diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java index dfb5220044f840e73ffda1aecc0aa2b9d3b77283..61ef6df4594aaa50e6ec3fcb19fd0f7862a53903 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java @@ -383,6 +383,14 @@ public class EventPumper implements Runnable { try { SocketChannel chan = servChan.accept(); chan.configureBlocking(false); + if (_context.blocklist().isBlocklisted(chan.socket().getInetAddress().getAddress())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Receive session request from blocklisted IP: " + chan.socket().getInetAddress()); + // need to add this stat first + // _context.statManager().addRateData("ntcp.connectBlocklisted", 1, 0); + try { chan.close(); } catch (IOException ioe) { } + return; + } SelectionKey ckey = chan.register(_selector, SelectionKey.OP_READ); NTCPConnection con = new NTCPConnection(_context, _transport, chan, ckey); if (_log.shouldLog(Log.DEBUG)) diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index bb35381ae77b9e87f8f6f9e0ca609f4351c71062..884947b11e07df0908bee7e89f753da4d204eb05 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -266,6 +266,11 @@ public class EstablishmentManager { state = (InboundEstablishState)_inboundStates.get(from); if (state == null) { + if (_context.blocklist().isBlocklisted(from.getIP())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Receive session request from blocklisted IP: " + from); + return; // drop the packet + } state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getLocalPort()); state.receiveSessionRequest(reader.getSessionRequestReader()); isNew = true; @@ -793,7 +798,16 @@ public class EstablishmentManager { sendCreated(inboundState); break; case InboundEstablishState.STATE_CONFIRMED_COMPLETELY: - if (inboundState.getConfirmedIdentity() != null) { + RouterIdentity remote = inboundState.getConfirmedIdentity(); + if (remote != null) { + if (_context.shitlist().isShitlistedForever(remote.calculateHash())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Dropping inbound connection from permanently shitlisted peer: " + remote.calculateHash().toBase64()); + // So next time we will not accept the con, rather than doing the whole handshake + _context.blocklist().add(inboundState.getSentIP()); + inboundState.fail(); + break; + } handleCompletelyEstablished(inboundState); break; } else {