* 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
This commit is contained in:
@ -67,6 +67,7 @@ Website files to change:
|
|||||||
index.html
|
index.html
|
||||||
index_de.html
|
index_de.html
|
||||||
hosts.txt (copy from mtn)
|
hosts.txt (copy from mtn)
|
||||||
|
release-x.y.z.html (new)
|
||||||
Sync with mtn.i2p2.i2p
|
Sync with mtn.i2p2.i2p
|
||||||
|
|
||||||
Copy news.xml to subscription location
|
Copy news.xml to subscription location
|
||||||
|
@ -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
|
2008-07-16 zzz
|
||||||
* configpeer.jsp: New
|
* configpeer.jsp: New
|
||||||
* i2psnark: Open completed files read-only the first time
|
* i2psnark: Open completed files read-only the first time
|
||||||
|
10
news.xml
10
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.
|
Improvements to applications like I2PSnark and the Router Console are also introduced.
|
||||||
</p>
|
</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:
|
2007-04-10:
|
||||||
|
749
router/java/src/net/i2p/router/Blocklist.java
Normal file
749
router/java/src/net/i2p/router/Blocklist.java
Normal file
@ -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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -259,6 +259,7 @@ public class Router {
|
|||||||
warmupCrypto();
|
warmupCrypto();
|
||||||
_sessionKeyPersistenceHelper.startup();
|
_sessionKeyPersistenceHelper.startup();
|
||||||
//_context.adminManager().startup();
|
//_context.adminManager().startup();
|
||||||
|
_context.blocklist().startup();
|
||||||
|
|
||||||
// let the timestamper get us sync'ed
|
// let the timestamper get us sync'ed
|
||||||
long before = System.currentTimeMillis();
|
long before = System.currentTimeMillis();
|
||||||
@ -380,8 +381,8 @@ public class Router {
|
|||||||
int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(),
|
int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(),
|
||||||
_context.bandwidthLimiter().getOutboundKBytesPerSecond());
|
_context.bandwidthLimiter().getOutboundKBytesPerSecond());
|
||||||
bwLim = (int)(((float)bwLim) * getSharePercentage());
|
bwLim = (int)(((float)bwLim) * getSharePercentage());
|
||||||
if (_log.shouldLog(Log.WARN))
|
if (_log.shouldLog(Log.INFO))
|
||||||
_log.warn("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps"));
|
_log.info("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps"));
|
||||||
|
|
||||||
if (bwLim < 12) {
|
if (bwLim < 12) {
|
||||||
ri.addCapability(CAPABILITY_BW12);
|
ri.addCapability(CAPABILITY_BW12);
|
||||||
|
@ -56,6 +56,7 @@ public class RouterContext extends I2PAppContext {
|
|||||||
private TunnelDispatcher _tunnelDispatcher;
|
private TunnelDispatcher _tunnelDispatcher;
|
||||||
private StatisticsManager _statPublisher;
|
private StatisticsManager _statPublisher;
|
||||||
private Shitlist _shitlist;
|
private Shitlist _shitlist;
|
||||||
|
private Blocklist _blocklist;
|
||||||
private MessageValidator _messageValidator;
|
private MessageValidator _messageValidator;
|
||||||
private MessageStateMonitor _messageStateMonitor;
|
private MessageStateMonitor _messageStateMonitor;
|
||||||
private RouterThrottle _throttle;
|
private RouterThrottle _throttle;
|
||||||
@ -126,6 +127,7 @@ public class RouterContext extends I2PAppContext {
|
|||||||
_tunnelDispatcher = new TunnelDispatcher(this);
|
_tunnelDispatcher = new TunnelDispatcher(this);
|
||||||
_statPublisher = new StatisticsManager(this);
|
_statPublisher = new StatisticsManager(this);
|
||||||
_shitlist = new Shitlist(this);
|
_shitlist = new Shitlist(this);
|
||||||
|
_blocklist = new Blocklist(this);
|
||||||
_messageValidator = new MessageValidator(this);
|
_messageValidator = new MessageValidator(this);
|
||||||
//_throttle = new RouterThrottleImpl(this);
|
//_throttle = new RouterThrottleImpl(this);
|
||||||
_throttle = new RouterDoSThrottle(this);
|
_throttle = new RouterDoSThrottle(this);
|
||||||
@ -249,6 +251,7 @@ public class RouterContext extends I2PAppContext {
|
|||||||
* who does this peer hate?
|
* who does this peer hate?
|
||||||
*/
|
*/
|
||||||
public Shitlist shitlist() { return _shitlist; }
|
public Shitlist shitlist() { return _shitlist; }
|
||||||
|
public Blocklist blocklist() { return _blocklist; }
|
||||||
/**
|
/**
|
||||||
* The router keeps track of messages it receives to prevent duplicates, as
|
* The router keeps track of messages it receives to prevent duplicates, as
|
||||||
* well as other criteria for "validity".
|
* well as other criteria for "validity".
|
||||||
|
@ -17,7 +17,7 @@ import net.i2p.CoreVersion;
|
|||||||
public class RouterVersion {
|
public class RouterVersion {
|
||||||
public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $";
|
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 String VERSION = "0.6.2";
|
||||||
public final static long BUILD = 9;
|
public final static long BUILD = 10;
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
||||||
System.out.println("Router ID: " + RouterVersion.ID);
|
System.out.println("Router ID: " + RouterVersion.ID);
|
||||||
|
@ -546,6 +546,15 @@ public class EstablishState {
|
|||||||
Signature sig = new Signature(s);
|
Signature sig = new Signature(s);
|
||||||
_verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey());
|
_verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey());
|
||||||
if (_verified) {
|
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))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug(prefix() + "verification successful for " + _con);
|
_log.debug(prefix() + "verification successful for " + _con);
|
||||||
|
|
||||||
|
@ -383,6 +383,14 @@ public class EventPumper implements Runnable {
|
|||||||
try {
|
try {
|
||||||
SocketChannel chan = servChan.accept();
|
SocketChannel chan = servChan.accept();
|
||||||
chan.configureBlocking(false);
|
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);
|
SelectionKey ckey = chan.register(_selector, SelectionKey.OP_READ);
|
||||||
NTCPConnection con = new NTCPConnection(_context, _transport, chan, ckey);
|
NTCPConnection con = new NTCPConnection(_context, _transport, chan, ckey);
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
@ -266,6 +266,11 @@ public class EstablishmentManager {
|
|||||||
|
|
||||||
state = (InboundEstablishState)_inboundStates.get(from);
|
state = (InboundEstablishState)_inboundStates.get(from);
|
||||||
if (state == null) {
|
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 = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getLocalPort());
|
||||||
state.receiveSessionRequest(reader.getSessionRequestReader());
|
state.receiveSessionRequest(reader.getSessionRequestReader());
|
||||||
isNew = true;
|
isNew = true;
|
||||||
@ -793,7 +798,16 @@ public class EstablishmentManager {
|
|||||||
sendCreated(inboundState);
|
sendCreated(inboundState);
|
||||||
break;
|
break;
|
||||||
case InboundEstablishState.STATE_CONFIRMED_COMPLETELY:
|
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);
|
handleCompletelyEstablished(inboundState);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
Reference in New Issue
Block a user