package net.i2p.router.tunnel; import java.io.File; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.router.util.DecayingBloomFilter; import net.i2p.router.util.DecayingHashSet; import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; import net.i2p.util.SystemVersion; /** * Manage the IV validation for all of the router's tunnels by way of a big * decaying bloom filter. * */ class BloomFilterIVValidator implements IVValidator { private final RouterContext _context; private final DecayingBloomFilter _filter; /** * After 2*halflife, an entry is completely forgotten from the bloom filter. * To avoid the issue of overlap within different tunnels, this is set * higher than it needs to be. * */ private static final int HALFLIFE_MS = 10*60*1000; private static final int MIN_SHARE_KBPS_TO_USE_BLOOM = 64; private static final int MIN_SHARE_KBPS_FOR_BIG_BLOOM = 512; private static final int MIN_SHARE_KBPS_FOR_HUGE_BLOOM = 1536; private static final int MIN_SHARE_KBPS_FOR_HUGE2_BLOOM = 4096; private static final int MIN_SHARE_KBPS_FOR_HUGE3_BLOOM = 8192; private static final long MIN_MEM_TO_USE_BLOOM = 64*1024*1024l; private static final long MIN_MEM_FOR_BIG_BLOOM = 128*1024*1024l; private static final long MIN_MEM_FOR_HUGE_BLOOM = 256*1024*1024l; private static final long MIN_MEM_FOR_HUGE2_BLOOM = 384*1024*1024l; private static final long MIN_MEM_FOR_HUGE3_BLOOM = 512*1024*1024l; /** for testing */ private static final String PROP_FORCE = "router.forceDecayingBloomFilter"; /** for testing */ private static final String PROP_DISABLE = "router.disableDecayingBloomFilter"; /** * @param KBps share bandwidth */ public BloomFilterIVValidator(RouterContext ctx, int KBps) { _context = ctx; // Select the filter based on share bandwidth and memory. // Note that at rates above 512KB, we increase the filter size // to keep acceptable false positive rates. // See DBF, BloomSHA1, and KeySelector for details. long maxMemory = SystemVersion.getMaxMemory(); if (_context.getBooleanProperty(PROP_FORCE)) { _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV"); // 2MB fixed } else if (_context.getBooleanProperty(PROP_DISABLE)) { _filter = null; } else if (KBps < MIN_SHARE_KBPS_TO_USE_BLOOM || maxMemory < MIN_MEM_TO_USE_BLOOM) { if (KBps >= MIN_SHARE_KBPS_TO_USE_BLOOM) warn(maxMemory, KBps, MIN_MEM_TO_USE_BLOOM, MIN_SHARE_KBPS_TO_USE_BLOOM); _filter = new DecayingHashSet(ctx, HALFLIFE_MS, 16, "TunnelIVV"); // appx. 4MB max } else if (KBps >= MIN_SHARE_KBPS_FOR_HUGE3_BLOOM && maxMemory >= MIN_MEM_FOR_HUGE3_BLOOM) { _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 27); // 32MB fixed } else if (KBps >= MIN_SHARE_KBPS_FOR_HUGE2_BLOOM && maxMemory >= MIN_MEM_FOR_HUGE2_BLOOM) { _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 26); // 16MB fixed if (KBps >= MIN_SHARE_KBPS_FOR_HUGE3_BLOOM) warn(maxMemory, KBps, MIN_MEM_FOR_HUGE3_BLOOM, MIN_SHARE_KBPS_FOR_HUGE3_BLOOM); } else if (KBps >= MIN_SHARE_KBPS_FOR_HUGE_BLOOM && maxMemory >= MIN_MEM_FOR_HUGE_BLOOM) { if (KBps >= MIN_SHARE_KBPS_FOR_HUGE2_BLOOM) warn(maxMemory, KBps, MIN_MEM_FOR_HUGE2_BLOOM, MIN_SHARE_KBPS_FOR_HUGE2_BLOOM); _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 25); // 8MB fixed } else if (KBps >= MIN_SHARE_KBPS_FOR_BIG_BLOOM && maxMemory >= MIN_MEM_FOR_BIG_BLOOM) { if (KBps >= MIN_SHARE_KBPS_FOR_HUGE_BLOOM) warn(maxMemory, KBps, MIN_MEM_FOR_HUGE_BLOOM, MIN_SHARE_KBPS_FOR_HUGE_BLOOM); _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 24); // 4MB fixed } else { if (KBps >= MIN_SHARE_KBPS_FOR_BIG_BLOOM) warn(maxMemory, KBps, MIN_MEM_FOR_BIG_BLOOM, MIN_SHARE_KBPS_FOR_BIG_BLOOM); _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV"); // 2MB fixed } ctx.statManager().createRateStat("tunnel.duplicateIV", "Note that a duplicate IV was received", "Tunnels", new long[] { 60*60*1000l }); } public boolean receiveIV(byte ivData[], int ivOffset, byte payload[], int payloadOffset) { if (_filter == null) // testing only return true; byte[] buf = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); DataHelper.xor(ivData, ivOffset, payload, payloadOffset, buf, 0, HopProcessor.IV_LENGTH); boolean dup = _filter.add(buf); SimpleByteCache.release(buf); if (dup) _context.statManager().addRateData("tunnel.duplicateIV", 1); return !dup; // return true if it is OK, false if it isn't } public void destroy() { if (_filter != null) _filter.stopDecaying(); } /** @since 0.9.20 */ private void warn(long maxMemory, int KBps, long recMaxMem, int threshKBps) { if (SystemVersion.isAndroid()) return; // Can't find any System property or wrapper property that gives // you the actual config file path, have to guess String path; if (SystemVersion.isLinuxService()) { path = "/etc/i2p"; } else { path = _context.getBaseDir().toString(); } String msg = "Configured for " + DataHelper.formatSize(KBps *1024L) + "Bps share bandwidth but only " + DataHelper.formatSize(maxMemory) + "B available memory."; if (_context.hasWrapper()) { msg += " Recommend increasing wrapper.java.maxmemory in " + path + File.separatorChar + "wrapper.config"; } else if (!SystemVersion.isWindows()) { msg += " Recommend increasing MAXMEMOPT in " + path + File.separatorChar + "runplain.sh or /usr/bin/i2prouter-nowrapper"; } else { msg += " Recommend running the restartable version of I2P, and increasing wrapper.java.maxmemory in " + path + File.separatorChar + "wrapper.config"; } // getMaxMemory() returns significantly lower than wrapper config, so add 10% msg += " to at least " + (recMaxMem * 11 / 10 / (1024*1024)) + " (MB)" + " if the actual share bandwidth exceeds " + DataHelper.formatSize(threshKBps * 1024L) + "Bps."; System.out.println("WARN: " + msg); _context.logManager().getLog(BloomFilterIVValidator.class).logAlways(Log.WARN, msg); } }