package net.i2p.router.networkdb; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.data.DataFormatException; import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterInfo; import net.i2p.data.SigningPrivateKey; import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; /** * Publish the local router's RouterInfo periodically. * NOTE - this also creates and signs the RI. * This is run immediately at startup... but doesn't really * send to the floodfills until the second time it runs. */ public class PublishLocalRouterInfoJob extends JobImpl { private final Log _log; /** * Don't store if somebody else stored it recently. * Must be less than PUBLISH_DELAY * 3 / 16 (see getDelay()) */ private static final long MIN_PUBLISH_DELAY = 9*60*1000; /** * Too short and the network puts a big connection load on the * floodfills since we store directly. * Too long and the floodfill will drop us - timeout is 60 minutes. */ private static final long PUBLISH_DELAY = 52*60*1000; /** this needs to be long enough to give us time to start up, but less than 20m (when we start accepting tunnels and could be a IBGW) Actually no, we need this soon if we are a new router or other routers have forgotten about us, else we can't build IB exploratory tunnels. */ private static final long FIRST_TIME_DELAY = 90*1000; private volatile boolean _notFirstTime; private final AtomicInteger _runCount = new AtomicInteger(); public PublishLocalRouterInfoJob(RouterContext ctx) { super(ctx); _log = ctx.logManager().getLog(PublishLocalRouterInfoJob.class); } public String getName() { return "Publish Local Router Info"; } public void runJob() { long last = getContext().netDb().getLastRouterInfoPublishTime(); long now = getContext().clock().now(); if (last + MIN_PUBLISH_DELAY > now) { long delay = getDelay(); requeue(last + delay - now); return; } RouterInfo oldRI = getContext().router().getRouterInfo(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Old routerInfo contains " + oldRI.getAddresses().size() + " addresses and " + oldRI.getOptionsMap().size() + " options"); try { List<RouterAddress> oldAddrs = new ArrayList<RouterAddress>(oldRI.getAddresses()); List<RouterAddress> newAddrs = getContext().commSystem().createAddresses(); int count = _runCount.incrementAndGet(); RouterInfo ri = new RouterInfo(oldRI); if (_notFirstTime && (count % 4) != 0 && oldAddrs.size() == newAddrs.size()) { // 3 times out of 4, we don't republish if everything is the same... // If something changed, including the cost, then publish, // otherwise don't. String newcaps = getContext().router().getCapabilities(); boolean different = !oldRI.getCapabilities().equals(newcaps); if (!different) { Comparator<RouterAddress> comp = new AddrComparator(); Collections.sort(oldAddrs, comp); Collections.sort(newAddrs, comp); for (int i = 0; i < oldAddrs.size(); i++) { // deepEquals() includes cost if (!oldAddrs.get(i).deepEquals(newAddrs.get(i))) { different = true; break; } } if (!different) { if (_log.shouldLog(Log.INFO)) _log.info("Not republishing early because costs and caps and addresses are the same"); requeue(getDelay()); return; } } if (_log.shouldLog(Log.INFO)) _log.info("Republishing early because addresses or costs or caps have changed -" + " oldCaps: " + oldRI.getCapabilities() + " newCaps: " + newcaps + " old:\n" + oldAddrs + "\nnew:\n" + newAddrs); } ri.setPublished(getContext().clock().now()); Properties stats = getContext().statPublisher().publishStatistics(); ri.setOptions(stats); ri.setAddresses(newAddrs); SigningPrivateKey key = getContext().keyManager().getSigningPrivateKey(); if (key == null) { _log.log(Log.CRIT, "Internal error - signing private key not known? rescheduling publish for 30s"); requeue(30*1000); return; } ri.sign(key); getContext().router().setRouterInfo(ri); if (_log.shouldLog(Log.INFO)) _log.info("Newly updated routerInfo is published with " + stats.size() + "/" + ri.getOptionsMap().size() + " options on " + new Date(ri.getPublished())); try { // This won't really publish until the netdb is initialized. getContext().netDb().publish(ri); } catch (IllegalArgumentException iae) { _log.log(Log.CRIT, "Error publishing our identity - corrupt? Restart required", iae); getContext().router().rebuildNewIdentity(); } } catch (DataFormatException dfe) { _log.error("Error signing the updated local router info!", dfe); } if (_notFirstTime) { requeue(getDelay()); } else { requeue(FIRST_TIME_DELAY); _notFirstTime = true; } } private long getDelay() { long rv = (PUBLISH_DELAY * 3 / 4) + getContext().random().nextLong(PUBLISH_DELAY / 4); // run 4x as often as usual publish time (see above) rv /= 4; return rv; } /** * Arbitrary sort so we can attempt to compare costs between two RIs to see if they have changed * * @since 0.9.18 */ private static class AddrComparator implements Comparator<RouterAddress>, Serializable { public int compare(RouterAddress l, RouterAddress r) { int c = l.getTransportStyle().compareTo(r.getTransportStyle()); if (c != 0) return c; String lh = l.getHost(); String rh = r.getHost(); if (lh == null) return rh == null ? 0 : -1; if (rh == null) return 1; return lh.compareTo(rh); } } }