* NetDB: Prep for leasesets with different expire times

- Add new I2CP RequestVariableLeaseSetMessage
   - Send RVLSM if client supports it; handle on client side;
     disabled by default for the moment.
   - Add LeaseSet.getLatestLeaseDate()
   - Check latest, not earliest, date too far in future in KNDF.validate()
   - Check latest date too far in past in KNDF.validate()
   - Only check gateway and tunnel ID for equality in OCMOSJ lease caching to reduce churn
   - Split up KNDF.validate(RI) for efficiency, don't need to check
     signature, netid, etc. before lookups, only on store
   - Remove enforeNetID config
   - Fix major bug causing newer leasesets to be treated as older, and not stored or published
   - Increase max adjustment time of earliest lease
   - TransientDataStore cleanups
   - RouterInfo and LeaseSet equals() speedups
This commit is contained in:
zzz
2013-06-09 14:42:51 +00:00
parent 182fe900b8
commit 68d25afcba
18 changed files with 432 additions and 120 deletions

View File

@ -17,7 +17,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -27,6 +26,7 @@ import java.util.TreeSet;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
@ -79,9 +79,8 @@ public class NetDbRenderer {
renderRouterInfo(buf, _context.router().getRouterInfo(), true, true);
} else {
boolean notFound = true;
Set routers = _context.netDb().getRouters();
for (Iterator iter = routers.iterator(); iter.hasNext(); ) {
RouterInfo ri = (RouterInfo)iter.next();
Set<RouterInfo> routers = _context.netDb().getRouters();
for (RouterInfo ri : routers) {
Hash key = ri.getIdentity().getHash();
if (key.toBase64().startsWith(routerPrefix)) {
renderRouterInfo(buf, ri, false, true);
@ -152,11 +151,12 @@ public class NetDbRenderer {
buf.append(dest.toBase64().substring(0, 6));
}
buf.append(")</b><br>\n");
long exp = ls.getEarliestLeaseDate()-now;
long exp = ls.getLatestLeaseDate()-now;
if (exp > 0)
buf.append(_("Expires in {0}", DataHelper.formatDuration2(exp))).append("<br>\n");
buf.append(_("Expires in {0}", DataHelper.formatDuration2(exp)));
else
buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exp))).append("<br>\n");
buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exp)));
buf.append("<br>\n");
if (debug) {
buf.append("RAP? " + ls.getReceivedAsPublished());
buf.append(" RAR? " + ls.getReceivedAsReply());
@ -171,9 +171,18 @@ public class NetDbRenderer {
buf.append("Encryption Key: ").append(ls.getEncryptionKey().toBase64().substring(0, 20)).append("...<br>");
}
for (int i = 0; i < ls.getLeaseCount(); i++) {
buf.append(_("Lease")).append(' ').append(i + 1).append(": " + _("Gateway") + ' ');
buf.append(_context.commSystem().renderPeerHTML(ls.getLease(i).getGateway()));
buf.append(' ' + _("Tunnel") + ' ').append(ls.getLease(i).getTunnelId().getTunnelId()).append("<br>\n");
Lease lease = ls.getLease(i);
buf.append(_("Lease")).append(' ').append(i + 1).append(": ").append(_("Gateway")).append(' ');
buf.append(_context.commSystem().renderPeerHTML(lease.getGateway()));
buf.append(' ').append(_("Tunnel")).append(' ').append(lease.getTunnelId().getTunnelId()).append(' ');
if (debug) {
long exl = lease.getEndDate().getTime() - now;
if (exl > 0)
buf.append(_("Expires in {0}", DataHelper.formatDuration2(exl)));
else
buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exl)));
}
buf.append("<br>\n");
}
buf.append("<hr>\n");
out.write(buf.toString());
@ -253,8 +262,7 @@ public class NetDbRenderer {
Set<RouterInfo> routers = new TreeSet(new RouterInfoComparator());
routers.addAll(_context.netDb().getRouters());
for (Iterator iter = routers.iterator(); iter.hasNext(); ) {
RouterInfo ri = (RouterInfo)iter.next();
for (RouterInfo ri : routers) {
Hash key = ri.getIdentity().getHash();
boolean isUs = key.equals(us);
if (!isUs) {
@ -391,8 +399,7 @@ public class NetDbRenderer {
buf.append(" title=\"").append(_(_context.commSystem().getCountryName(country))).append('\"');
buf.append(" src=\"/flags.jsp?c=").append(country).append("\"> ");
}
for (Iterator iter = info.getAddresses().iterator(); iter.hasNext(); ) {
RouterAddress addr = (RouterAddress)iter.next();
for (RouterAddress addr : info.getAddresses()) {
String style = addr.getTransportStyle();
buf.append("<b>").append(DataHelper.stripHTML(style)).append(":</b> ");
int cost = addr.getCost();

View File

@ -16,6 +16,7 @@ import net.i2p.data.i2cp.DisconnectMessage;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.data.i2cp.SetDateMessage;
@ -40,6 +41,7 @@ class I2PClientMessageHandlerMap {
highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE);
highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE);
highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE);
highest = Math.max(highest, RequestVariableLeaseSetMessage.MESSAGE_TYPE);
_handlers = new I2CPMessageHandler[highest+1];
_handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context);
@ -50,6 +52,7 @@ class I2PClientMessageHandlerMap {
_handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context);
_handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
_handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
_handlers[RequestVariableLeaseSetMessage.MESSAGE_TYPE] = new RequestVariableLeaseSetMessageHandler(context);
}
public I2CPMessageHandler getHandler(int messageTypeId) {

View File

@ -29,7 +29,8 @@ import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.util.Log;
/**
* Handle I2CP RequestLeaseSetMessage from the router by granting all leases
* Handle I2CP RequestLeaseSetMessage from the router by granting all leases,
* using the specified expiration time for each lease.
*
* @author jrandom
*/
@ -37,7 +38,15 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
private final Map<Destination, LeaseInfo> _existingLeaseSets;
public RequestLeaseSetMessageHandler(I2PAppContext context) {
super(context, RequestLeaseSetMessage.MESSAGE_TYPE);
this(context, RequestLeaseSetMessage.MESSAGE_TYPE);
}
/**
* For extension
* @since 0.9.7
*/
protected RequestLeaseSetMessageHandler(I2PAppContext context, int messageType) {
super(context, messageType);
// not clear why there would ever be more than one
_existingLeaseSets = new ConcurrentHashMap(4);
}
@ -55,6 +64,14 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
//lease.setStartDate(msg.getStartDate());
leaseSet.addLease(lease);
}
signLeaseSet(leaseSet, session);
}
/**
* Finish creating and signing the new LeaseSet
* @since 0.9.7
*/
protected void signLeaseSet(LeaseSet leaseSet, I2PSessionImpl session) {
// also, if this session is connected to multiple routers, include other leases here
leaseSet.setDestination(session.getMyDestination());

View File

@ -0,0 +1,41 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* 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 net.i2p.I2PAppContext;
import net.i2p.data.LeaseSet;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
import net.i2p.util.Log;
/**
* Handle I2CP RequestVariableLeaseSetMessage from the router by granting all leases,
* retaining the individual expiration time for each lease.
*
* @since 0.9.7
*/
class RequestVariableLeaseSetMessageHandler extends RequestLeaseSetMessageHandler {
public RequestVariableLeaseSetMessageHandler(I2PAppContext context) {
super(context, RequestVariableLeaseSetMessage.MESSAGE_TYPE);
}
@Override
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle message " + message);
RequestVariableLeaseSetMessage msg = (RequestVariableLeaseSetMessage) message;
LeaseSet leaseSet = new LeaseSet();
for (int i = 0; i < msg.getEndpoints(); i++) {
leaseSet.addLease(msg.getEndpoint(i));
}
signLeaseSet(leaseSet, session);
}
}

View File

@ -93,6 +93,9 @@ public class LeaseSet extends DatabaseEntry {
_firstExpiration = Long.MAX_VALUE;
}
/**
* Same as getEarliestLeaseDate()
*/
public long getDate() {
return getEarliestLeaseDate();
}
@ -198,7 +201,7 @@ public class LeaseSet extends DatabaseEntry {
}
/**
* Retrieve the end date of the earliest lease include in this leaseSet.
* Retrieve the end date of the earliest lease included in this leaseSet.
* This is the date that should be used in comparisons for leaseSet age - to
* determine which LeaseSet was published more recently (later earliestLeaseSetDate
* means it was published later)
@ -211,6 +214,17 @@ public class LeaseSet extends DatabaseEntry {
return _firstExpiration;
}
/**
* Retrieve the end date of the latest lease included in this leaseSet.
* This is the date used in isCurrent().
*
* @return latest end date of any lease in the set, or 0 if there are no leases
* @since 0.9.7
*/
public long getLatestLeaseDate() {
return _lastExpiration;
}
/**
* Verify that the signature matches the lease set's destination's signing public key.
* OR the included revocation key.
@ -287,8 +301,12 @@ public class LeaseSet extends DatabaseEntry {
/**
* This does NOT validate the signature
*
* @throws IllegalStateException if called more than once or Destination already set
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
if (_destination != null)
throw new IllegalStateException();
_destination = new Destination();
_destination.readBytes(in);
_encryptionKey = PublicKey.create(in);
@ -297,7 +315,6 @@ public class LeaseSet extends DatabaseEntry {
if (numLeases > MAX_LEASES)
throw new DataFormatException("Too many leases - max is " + MAX_LEASES);
//_version = DataHelper.readLong(in, 4);
_leases.clear();
for (int i = 0; i < numLeases; i++) {
Lease lease = new Lease();
lease.readBytes(in);
@ -344,9 +361,10 @@ public class LeaseSet extends DatabaseEntry {
if (object == this) return true;
if ((object == null) || !(object instanceof LeaseSet)) return false;
LeaseSet ls = (LeaseSet) object;
return DataHelper.eq(getEncryptionKey(), ls.getEncryptionKey()) &&
//DataHelper.eq(getVersion(), ls.getVersion()) &&
DataHelper.eq(_leases, ls._leases) && DataHelper.eq(_signature, ls.getSignature())
return
DataHelper.eq(_signature, ls.getSignature())
&& DataHelper.eq(_leases, ls._leases)
&& DataHelper.eq(getEncryptionKey(), ls.getEncryptionKey())
&& DataHelper.eq(_signingKey, ls.getSigningKey())
&& DataHelper.eq(_destination, ls.getDestination());
@ -371,7 +389,7 @@ public class LeaseSet extends DatabaseEntry {
buf.append("\n\tSignature: ").append(_signature);
buf.append("\n\tLeases: #").append(getLeaseCount());
for (int i = 0; i < getLeaseCount(); i++)
buf.append("\n\t\tLease (").append(i).append("): ").append(getLease(i));
buf.append("\n\t\t").append(getLease(i));
buf.append("]");
return buf.toString();
}

View File

@ -592,9 +592,10 @@ public class RouterInfo extends DatabaseEntry {
if (object == this) return true;
if ((object == null) || !(object instanceof RouterInfo)) return false;
RouterInfo info = (RouterInfo) object;
return DataHelper.eq(_identity, info.getIdentity())
return
_published == info.getPublished()
&& DataHelper.eq(_signature, info.getSignature())
&& _published == info.getPublished();
&& DataHelper.eq(_identity, info.getIdentity());
// Let's speed up the NetDB
//&& DataHelper.eq(_addresses, info.getAddresses())
//&& DataHelper.eq(_options, info.getOptions())

View File

@ -77,6 +77,8 @@ public class I2CPMessageHandler {
return new ReportAbuseMessage();
case RequestLeaseSetMessage.MESSAGE_TYPE:
return new RequestLeaseSetMessage();
case RequestVariableLeaseSetMessage.MESSAGE_TYPE:
return new RequestVariableLeaseSetMessage();
case SendMessageMessage.MESSAGE_TYPE:
return new SendMessageMessage();
case SendMessageExpiresMessage.MESSAGE_TYPE:

View File

@ -50,18 +50,18 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl {
}
public Hash getRouter(int endpoint) {
if ((endpoint < 0) || (_endpoints.size() < endpoint)) return null;
if ((endpoint < 0) || (_endpoints.size() <= endpoint)) return null;
return _endpoints.get(endpoint).getRouter();
}
public TunnelId getTunnelId(int endpoint) {
if ((endpoint < 0) || (_endpoints.size() < endpoint)) return null;
if ((endpoint < 0) || (_endpoints.size() <= endpoint)) return null;
return _endpoints.get(endpoint).getTunnelId();
}
/** @deprecated unused - presumably he meant remove? */
public void remoteEndpoint(int endpoint) {
if ((endpoint >= 0) && (endpoint < _endpoints.size())) _endpoints.remove(endpoint);
if ((endpoint >= 0) && (endpoint <= _endpoints.size())) _endpoints.remove(endpoint);
}
public void addEndpoint(Hash router, TunnelId tunnel) {

View File

@ -0,0 +1,128 @@
package net.i2p.data.i2cp;
/*
* 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Lease;
import net.i2p.util.VersionComparator;
/**
* Defines the message a router sends to a client to request that
* a leaseset be created and signed. The reply is a CreateLeaseSetMessage.
*
* This message has an expiration time for each lease, unlike RequestLeaseSetMessage,
* which has a single expiration time for all leases.
*
* @since 0.9.7
*/
public class RequestVariableLeaseSetMessage extends I2CPMessageImpl {
public final static int MESSAGE_TYPE = 37;
private SessionId _sessionId;
private final List<Lease> _endpoints;
private static final String MIN_VERSION = "0.9.7";
public RequestVariableLeaseSetMessage() {
_endpoints = new ArrayList();
}
/**
* Does the client support this message?
*
* @param clientVersion may be null
* @return version != null and version >= 0.9.7
*/
public static boolean isSupported(String clientVersion) {
return clientVersion != null &&
VersionComparator.comp(clientVersion, MIN_VERSION) >= 0;
}
public SessionId getSessionId() {
return _sessionId;
}
public void setSessionId(SessionId id) {
_sessionId = id;
}
public int getEndpoints() {
return _endpoints.size();
}
public Lease getEndpoint(int endpoint) {
if ((endpoint < 0) || (_endpoints.size() <= endpoint)) return null;
return _endpoints.get(endpoint);
}
public void addEndpoint(Lease lease) {
if (lease == null)
throw new IllegalArgumentException();
_endpoints.add(lease);
}
@Override
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
if (_sessionId == null)
throw new IllegalStateException();
_sessionId = new SessionId();
_sessionId.readBytes(in);
int numTunnels = (int) DataHelper.readLong(in, 1);
for (int i = 0; i < numTunnels; i++) {
Lease lease = new Lease();
lease.readBytes(in);
_endpoints.add(lease);
}
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
@Override
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_sessionId == null)
throw new I2CPMessageException("No data");
ByteArrayOutputStream os = new ByteArrayOutputStream(256);
try {
_sessionId.writeBytes(os);
DataHelper.writeLong(os, 1, _endpoints.size());
for (int i = 0; i < _endpoints.size(); i++) {
_endpoints.get(i).writeBytes(os);
}
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() {
return MESSAGE_TYPE;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[RequestVariableLeaseSetMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tTunnels:");
for (int i = 0; i < getEndpoints(); i++) {
buf.append('\n').append(_endpoints.get(i));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -1,3 +1,20 @@
2013-06-09 zzz
* NetDB: Prep for leasesets with different expire times
- Add new I2CP RequestVariableLeaseSetMessage
- Send RVLSM if client supports it; handle on client side;
disabled by default for the moment.
- Add LeaseSet.getLatestLeaseDate()
- Check latest, not earliest, date too far in future in KNDF.validate()
- Check latest date too far in past in KNDF.validate()
- Only check gateway and tunnel ID for equality in OCMOSJ lease caching to reduce churn
- Split up KNDF.validate(RI) for efficiency, don't need to check
signature, netid, etc. before lookups, only on store
- Remove enforeNetID config
- Fix major bug causing newer leasesets to be treated as older, and not stored or published
- Increase max adjustment time of earliest lease
- TransientDataStore cleanups
- RouterInfo and LeaseSet equals() speedups
2013-06-07 zzz
* BlockfileNamingService:
- Fix bug that kept reverse index from being updated

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 6;
public final static long BUILD = 7;
/** for example "-test" */
public final static String EXTRA = "";

View File

@ -10,9 +10,12 @@ package net.i2p.router.client;
import java.util.Date;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
@ -31,6 +34,12 @@ class RequestLeaseSetJob extends JobImpl {
private final ClientConnectionRunner _runner;
private final LeaseRequestState _requestState;
private static final long MAX_FUDGE = 2*1000;
/** temp for testing */
private static final String PROP_VARIABLE = "router.variableLeaseExpiration";
private static final boolean DFLT_VARIABLE = false;
public RequestLeaseSetJob(RouterContext ctx, ClientConnectionRunner runner, LeaseRequestState state) {
super(ctx);
_log = ctx.logManager().getLog(RequestLeaseSetJob.class);
@ -44,22 +53,51 @@ class RequestLeaseSetJob extends JobImpl {
public void runJob() {
if (_runner.isDead()) return;
RequestLeaseSetMessage msg = new RequestLeaseSetMessage();
long endTime = _requestState.getRequested().getEarliestLeaseDate();
// Add a small number of ms (0-300) that increases as we approach the expire time.
LeaseSet requested = _requestState.getRequested();
long endTime = requested.getEarliestLeaseDate();
// Add a small number of ms (0 to MAX_FUDGE) that increases as we approach the expire time.
// Since the earliest date functions as a version number,
// this will force the floodfill to flood each new version;
// otherwise it won't if the earliest time hasn't changed.
long fudge = 300 - ((endTime - getContext().clock().now()) / 2000);
long fudge = MAX_FUDGE - ((endTime - getContext().clock().now()) / (10*60*1000 / MAX_FUDGE));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Adding fudge " + fudge);
endTime += fudge;
Date end = new Date(endTime);
msg.setEndDate(end);
msg.setSessionId(_runner.getSessionId());
for (int i = 0; i < _requestState.getRequested().getLeaseCount(); i++) {
msg.addEndpoint(_requestState.getRequested().getLease(i).getGateway(),
_requestState.getRequested().getLease(i).getTunnelId());
I2CPMessage msg;
if (getContext().getProperty(PROP_VARIABLE, DFLT_VARIABLE) &&
(_runner instanceof QueuedClientConnectionRunner ||
RequestVariableLeaseSetMessage.isSupported(_runner.getClientVersion()))) {
// new style - leases will have individual expirations
RequestVariableLeaseSetMessage rmsg = new RequestVariableLeaseSetMessage();
rmsg.setSessionId(_runner.getSessionId());
for (int i = 0; i < requested.getLeaseCount(); i++) {
Lease lease = requested.getLease(i);
if (lease.getEndDate().getTime() < endTime) {
// don't modify old object, we don't know where it came from
Lease nl = new Lease();
nl.setGateway(lease.getGateway());
nl.setTunnelId(lease.getTunnelId());
nl.setEndDate(new Date(endTime));
lease = nl;
//if (_log.shouldLog(Log.INFO))
// _log.info("Adjusted end date to " + endTime + " for " + lease);
}
rmsg.addEndpoint(lease);
}
msg = rmsg;
} else {
// old style - all leases will have same expiration
RequestLeaseSetMessage rmsg = new RequestLeaseSetMessage();
Date end = new Date(endTime);
rmsg.setEndDate(end);
rmsg.setSessionId(_runner.getSessionId());
for (int i = 0; i < requested.getLeaseCount(); i++) {
Lease lease = requested.getLease(i);
rmsg.addEndpoint(lease.getGateway(),
lease.getTunnelId());
}
msg = rmsg;
}
try {

View File

@ -304,7 +304,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
// it (due to failure for example) we won't continue to use it.
for (int i = 0; i < _leaseSet.getLeaseCount(); i++) {
Lease lease = _leaseSet.getLease(i);
if (_lease.equals(lease)) {
// Don't use Lease.equals(), as that compares expiration time too,
// and that time may change in subsequent publication
//if (_lease.equals(lease)) {
if (_lease.getTunnelId().equals(lease.getTunnelId()) &&
_lease.getGateway().equals(lease.getGateway())) {
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Found in cache - lease for " + _toString);
return true;

View File

@ -96,10 +96,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
/** don't probe or broadcast data, just respond and search when explicitly needed */
private static final boolean QUIET = false;
public static final String PROP_ENFORCE_NETID = "router.networkDatabase.enforceNetId";
private static final boolean DEFAULT_ENFORCE_NETID = false;
private boolean _enforceNetId = DEFAULT_ENFORCE_NETID;
public final static String PROP_DB_DIR = "router.networkDatabase.dbDir";
public final static String DEFAULT_DB_DIR = "netDb";
@ -143,7 +139,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_peerSelector = createPeerSelector();
_publishingLeaseSets = new HashMap(8);
_activeRequests = new HashMap(8);
_enforceNetId = DEFAULT_ENFORCE_NETID;
_reseedChecker = new ReseedChecker(context);
context.statManager().createRateStat("netDb.lookupDeferred", "how many lookups are deferred?", "NetworkDatabase", new long[] { 60*60*1000 });
context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 });
@ -223,11 +218,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_log.info("No DB dir specified [" + PROP_DB_DIR + "], using [" + DEFAULT_DB_DIR + "]");
_dbDir = DEFAULT_DB_DIR;
}
String enforce = _context.getProperty(PROP_ENFORCE_NETID);
if (enforce != null)
_enforceNetId = Boolean.parseBoolean(enforce);
else
_enforceNetId = DEFAULT_ENFORCE_NETID;
_ds.restart();
_exploreKeys.clear();
@ -249,12 +239,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_log.info("Starting up the kademlia network database");
RouterInfo ri = _context.router().getRouterInfo();
String dbDir = _context.getProperty(PROP_DB_DIR, DEFAULT_DB_DIR);
String enforce = _context.getProperty(PROP_ENFORCE_NETID);
if (enforce != null)
_enforceNetId = Boolean.parseBoolean(enforce);
else
_enforceNetId = DEFAULT_ENFORCE_NETID;
_kb = new KBucketSet(_context, ri.getIdentity().getHash());
try {
_ds = new PersistentDataStore(_context, dbDir, this);
@ -443,7 +427,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
fail(key);
} else if (rv.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
try {
if (validate(key, (RouterInfo)rv) == null)
if (validate((RouterInfo)rv) == null)
return rv;
} catch (IllegalArgumentException iae) {}
fail(key);
@ -512,7 +496,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
// startup allows some lax rules).
boolean valid = true;
try {
valid = (null == validate(key, (RouterInfo)ds));
valid = (null == validate((RouterInfo)ds));
} catch (IllegalArgumentException iae) {
valid = false;
}
@ -531,6 +515,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
private static final long PUBLISH_DELAY = 3*1000;
public void publish(LeaseSet localLeaseSet) {
if (!_initialized) return;
Hash h = localLeaseSet.getDestination().calculateHash();
@ -564,6 +549,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
// remove first since queue is a TreeSet now...
_context.jobQueue().removeJob(j);
j.getTiming().setStartAfter(nextTime);
if (_log.shouldLog(Log.INFO))
_log.info("Queueing to publish at " + (new Date(nextTime)) + ' ' + localLeaseSet);
_context.jobQueue().addJob(j);
}
@ -627,34 +614,46 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
* Determine whether this leaseSet will be accepted as valid and current
* given what we know now.
*
* TODO this is called several times, only check the key and signature once
* Unlike for RouterInfos, this is only called once, when stored.
* After that, LeaseSet.isCurrent() is used.
*
* @return reason why the entry is not valid, or null if it is valid
*/
String validate(Hash key, LeaseSet leaseSet) {
private String validate(Hash key, LeaseSet leaseSet) {
if (!key.equals(leaseSet.getDestination().calculateHash())) {
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid store attempt! key does not match leaseSet.destination! key = "
+ key + ", leaseSet = " + leaseSet);
return "Key does not match leaseSet.destination - " + key.toBase64();
} else if (!leaseSet.verifySignature()) {
}
if (!leaseSet.verifySignature()) {
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid leaseSet signature! leaseSet = " + leaseSet);
return "Invalid leaseSet signature on " + leaseSet.getDestination().calculateHash().toBase64();
} else if (leaseSet.getEarliestLeaseDate() <= _context.clock().now() - 2*Router.CLOCK_FUDGE_FACTOR) {
long age = _context.clock().now() - leaseSet.getEarliestLeaseDate();
}
long earliest = leaseSet.getEarliestLeaseDate();
long latest = leaseSet.getLatestLeaseDate();
long now = _context.clock().now();
if (earliest <= now - 2*Router.CLOCK_FUDGE_FACTOR ||
// same as the isCurrent(Router.CLOCK_FUDGE_FACTOR) test in
// lookupLeaseSetLocally()
latest <= now - Router.CLOCK_FUDGE_FACTOR) {
long age = now - earliest;
if (_log.shouldLog(Log.WARN))
_log.warn("Old leaseSet! not storing it: "
+ leaseSet.getDestination().calculateHash().toBase64()
+ " expires on " + new Date(leaseSet.getEarliestLeaseDate()), new Exception("Rejecting store"));
+ leaseSet.getDestination().calculateHash()
+ " first exp. " + new Date(earliest)
+ " last exp. " + new Date(latest),
new Exception("Rejecting store"));
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash().toBase64()
+ " expired " + DataHelper.formatDuration(age) + " ago";
} else if (leaseSet.getEarliestLeaseDate() > _context.clock().now() + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) {
long age = leaseSet.getEarliestLeaseDate() - _context.clock().now();
}
if (latest > now + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) {
long age = latest - now;
// let's not make this an error, it happens when peers have bad clocks
if (_log.shouldLog(Log.WARN))
_log.warn("LeaseSet expires too far in the future: "
+ leaseSet.getDestination().calculateHash().toBase64()
+ leaseSet.getDestination().calculateHash()
+ " expires " + DataHelper.formatDuration(age) + " from now");
return "Future expiring leaseSet for " + leaseSet.getDestination().calculateHash()
+ " expiring in " + DataHelper.formatDuration(age);
@ -720,11 +719,40 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
* Determine whether this routerInfo will be accepted as valid and current
* given what we know now.
*
* TODO this is called several times, only check the key and signature once
* Call this only on first store, to check the key and signature once
*
* @return reason why the entry is not valid, or null if it is valid
*/
String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
private String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
if (!key.equals(routerInfo.getIdentity().getHash())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid store attempt! key does not match routerInfo.identity! key = " + key + ", router = " + routerInfo);
return "Key does not match routerInfo.identity";
}
if (!routerInfo.isValid()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
return "Invalid routerInfo signature";
}
if (routerInfo.getNetworkId() != Router.NETWORK_ID){
_context.banlist().banlistRouter(key, "Not in our network");
if (_log.shouldLog(Log.WARN))
_log.warn("Bad network: " + routerInfo);
return "Not in our network";
}
return validate(routerInfo);
}
/**
* Determine whether this routerInfo will be accepted as valid and current
* given what we know now.
*
* Call this before each use, to check expiration
*
* @return reason why the entry is not valid, or null if it is valid
* @since 0.9.7
*/
private String validate(RouterInfo routerInfo) throws IllegalArgumentException {
long now = _context.clock().now();
boolean upLongEnough = _context.router().getUptime() > 60*60*1000;
// Once we're over MIN_ROUTERS routers, reduce the expiration time down from the default,
@ -743,59 +771,44 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
ROUTER_INFO_EXPIRATION_MIN +
((ROUTER_INFO_EXPIRATION - ROUTER_INFO_EXPIRATION_MIN) * MIN_ROUTERS / (_kb.size() + 1)));
if (!key.equals(routerInfo.getIdentity().getHash())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid store attempt! key does not match routerInfo.identity! key = " + key + ", router = " + routerInfo);
return "Key does not match routerInfo.identity - " + key.toBase64();
} else if (!routerInfo.isValid()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
return "Invalid routerInfo signature on " + key.toBase64();
} else if (upLongEnough && !routerInfo.isCurrent(adjustedExpiration)) {
if (routerInfo.getNetworkId() != Router.NETWORK_ID) {
_context.banlist().banlistRouter(key, "Peer is not in our network");
return "Peer is not in our network (" + routerInfo.getNetworkId() + ", wants "
+ Router.NETWORK_ID + "): " + routerInfo.calculateHash().toBase64();
}
if (upLongEnough && !routerInfo.isCurrent(adjustedExpiration)) {
long age = _context.clock().now() - routerInfo.getPublished();
int existing = _kb.size();
if (existing >= MIN_REMAINING_ROUTERS) {
if (_log.shouldLog(Log.INFO))
_log.info("Not storing expired router for " + key.toBase64(), new Exception("Rejecting store"));
return "Peer " + key.toBase64() + " expired " + DataHelper.formatDuration(age) + " ago";
_log.info("Not storing expired RI " + routerInfo.getIdentity().getHash(), new Exception("Rejecting store"));
return "Peer expired " + DataHelper.formatDuration(age) + " ago";
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Even though the peer is old, we have only " + existing
+ " peers left (curPeer: " + key.toBase64() + " published on "
+ new Date(routerInfo.getPublished()));
+ " peers left " + routerInfo);
}
} else if (routerInfo.getPublished() > now + 2*Router.CLOCK_FUDGE_FACTOR) {
}
if (routerInfo.getPublished() > now + 2*Router.CLOCK_FUDGE_FACTOR) {
long age = routerInfo.getPublished() - _context.clock().now();
if (_log.shouldLog(Log.INFO))
_log.info("Peer " + key.toBase64() + " published their routerInfo in the future?! ["
_log.info("Peer " + routerInfo.getIdentity().getHash() + " published their routerInfo in the future?! ["
+ new Date(routerInfo.getPublished()) + "]", new Exception("Rejecting store"));
return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " in the future?!";
} else if (_enforceNetId && (routerInfo.getNetworkId() != Router.NETWORK_ID) ){
String rv = "Peer " + key.toBase64() + " is from another network, not accepting it (id="
+ routerInfo.getNetworkId() + ", want " + Router.NETWORK_ID + ")";
return rv;
} else if (upLongEnough && (routerInfo.getPublished() < now - 2*24*60*60*1000l) ) {
return "Peer published " + DataHelper.formatDuration(age) + " in the future?!";
}
if (upLongEnough && (routerInfo.getPublished() < now - 2*24*60*60*1000l) ) {
long age = _context.clock().now() - routerInfo.getPublished();
return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " ago";
} else if (upLongEnough && !routerInfo.isCurrent(ROUTER_INFO_EXPIRATION_SHORT)) {
return "Peer published " + DataHelper.formatDuration(age) + " ago";
}
if (upLongEnough && !routerInfo.isCurrent(ROUTER_INFO_EXPIRATION_SHORT)) {
if (routerInfo.getAddresses().isEmpty())
return "Peer " + key.toBase64() + " published > 75m ago with no addresses";
return "Peer published > 75m ago with no addresses";
// This should cover the introducers case below too
// And even better, catches the case where the router is unreachable but knows no introducers
if (routerInfo.getCapabilities().indexOf(Router.CAPABILITY_UNREACHABLE) >= 0)
return "Peer " + key.toBase64() + " published > 75m ago and thinks it is unreachable";
return "Peer published > 75m ago and thinks it is unreachable";
RouterAddress ra = routerInfo.getTargetAddress("SSU");
if (ra != null) {
// Introducers change often, introducee will ping introducer for 2 hours
if (ra.getOption("ihost0") != null)
return "Peer " + key.toBase64() + " published > 75m ago with SSU Introducers";
return "Peer published > 75m ago with SSU Introducers";
if (routerInfo.getTargetAddress("NTCP") == null)
return "Peer " + key.toBase64() + " published > 75m ago, SSU only without introducers";
return "Peer published > 75m ago, SSU only without introducers";
}
}
return null;

View File

@ -47,12 +47,12 @@ public class RepublishLeaseSetJob extends JobImpl {
if (getContext().clientManager().isLocal(_dest)) {
LeaseSet ls = _facade.lookupLeaseSetLocally(_dest);
if (ls != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Client " + _dest + " is local, so we're republishing it");
if (!ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not publishing a LOCAL lease that isn't current - " + _dest, new Exception("Publish expired LOCAL lease?"));
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Publishing " + ls);
getContext().statManager().addRateData("netDb.republishLeaseSetCount", 1, 0);
_facade.sendStore(_dest, ls, new OnRepublishSuccess(getContext()), new OnRepublishFailure(getContext(), this), REPUBLISH_LEASESET_TIMEOUT, null);
_lastPublished = getContext().clock().now();

View File

@ -81,9 +81,11 @@ class TransientDataStore implements DataStore {
return Collections.unmodifiableSet(_data.entrySet());
}
/** for PersistentDataStore only - don't use here @throws IAE always */
/** for PersistentDataStore only - don't use here
* @throws UnsupportedOperationException always
*/
public DatabaseEntry get(Hash key, boolean persist) {
throw new IllegalArgumentException("no");
throw new UnsupportedOperationException();
}
public DatabaseEntry get(Hash key) {
@ -103,9 +105,11 @@ class TransientDataStore implements DataStore {
return count;
}
/** for PersistentDataStore only - don't use here @throws IAE always */
/** for PersistentDataStore only - don't use here
* @throws UnsupportedOperationException always
*/
public boolean put(Hash key, DatabaseEntry data, boolean persist) {
throw new IllegalArgumentException("no");
throw new UnsupportedOperationException();
}
/**
@ -116,8 +120,7 @@ class TransientDataStore implements DataStore {
if (data == null) return false;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Storing key " + key);
DatabaseEntry old = null;
old = _data.putIfAbsent(key, data);
DatabaseEntry old = _data.putIfAbsent(key, data);
boolean rv = false;
if (data.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
// Don't do this here so we don't reset it at router startup;
@ -128,13 +131,15 @@ class TransientDataStore implements DataStore {
RouterInfo ori = (RouterInfo)old;
if (ri.getPublished() < ori.getPublished()) {
if (_log.shouldLog(Log.INFO))
_log.info("Almost clobbered an old router! " + key + ": [old published on " + new Date(ori.getPublished()) + " new on " + new Date(ri.getPublished()) + "]");
_log.info("Almost clobbered an old router! " + key + ": [old published on " + new Date(ori.getPublished()) +
" new on " + new Date(ri.getPublished()) + ']');
} else if (ri.getPublished() == ori.getPublished()) {
if (_log.shouldLog(Log.INFO))
_log.info("Duplicate " + key);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Updated the old router for " + key + ": [old published on " + new Date(ori.getPublished()) + " new on " + new Date(ri.getPublished()) + "]");
_log.info("Updated the old router for " + key + ": [old published on " + new Date(ori.getPublished()) +
" new on " + new Date(ri.getPublished()) + ']');
_data.put(key, data);
rv = true;
}
@ -149,13 +154,15 @@ class TransientDataStore implements DataStore {
LeaseSet ols = (LeaseSet)old;
if (ls.getEarliestLeaseDate() < ols.getEarliestLeaseDate()) {
if (_log.shouldLog(Log.INFO))
_log.info("Almost clobbered an old leaseSet! " + key + ": [old published on " + new Date(ols.getEarliestLeaseDate()) + " new on " + new Date(ls.getEarliestLeaseDate()) + "]");
_log.info("Almost clobbered an old leaseSet! " + key + ": [old expires " + new Date(ols.getEarliestLeaseDate()) +
" new on " + new Date(ls.getEarliestLeaseDate()) + ']');
} else if (ls.getEarliestLeaseDate() == ols.getEarliestLeaseDate()) {
if (_log.shouldLog(Log.INFO))
_log.info("Duplicate " + key);
} else {
if (_log.shouldLog(Log.INFO)) {
_log.info("Updated old leaseSet " + key + ": [old published on " + new Date(ols.getEarliestLeaseDate()) + " new on " + new Date(ls.getEarliestLeaseDate()) + "]");
_log.info("Updated old leaseSet " + key + ": [old expires " + new Date(ols.getEarliestLeaseDate()) +
" new on " + new Date(ls.getEarliestLeaseDate()) + ']');
if (_log.shouldLog(Log.DEBUG))
_log.debug("RAP? " + ls.getReceivedAsPublished() + " RAR? " + ls.getReceivedAsReply());
}
@ -164,7 +171,7 @@ class TransientDataStore implements DataStore {
}
} else {
if (_log.shouldLog(Log.INFO)) {
_log.info("New leaseset for " + key + ": published on " + new Date(ls.getEarliestLeaseDate()));
_log.info("New leaseset for " + key + ": expires " + new Date(ls.getEarliestLeaseDate()));
if (_log.shouldLog(Log.DEBUG))
_log.debug("RAP? " + ls.getReceivedAsPublished() + " RAR? " + ls.getReceivedAsReply());
}
@ -187,9 +194,11 @@ class TransientDataStore implements DataStore {
return buf.toString();
}
/** for PersistentDataStore only - don't use here */
/** for PersistentDataStore only - don't use here
* @throws UnsupportedOperationException always
*/
public DatabaseEntry remove(Hash key, boolean persist) {
throw new IllegalArgumentException("no");
throw new UnsupportedOperationException();
}
public DatabaseEntry remove(Hash key) {

View File

@ -16,6 +16,9 @@ class ExpireJob extends JobImpl {
private boolean _leaseUpdated;
private final long _dropAfter;
private static final long OB_EARLY_EXPIRE = 30*1000;
private static final long IB_EARLY_EXPIRE = OB_EARLY_EXPIRE + 7500;
public ExpireJob(RouterContext ctx, TunnelCreatorConfig cfg, TunnelPool pool) {
super(ctx);
_pool = pool;
@ -27,9 +30,11 @@ class ExpireJob extends JobImpl {
// Also skew the inbound away from the outbound
long expire = cfg.getExpiration();
_dropAfter = expire + Router.CLOCK_FUDGE_FACTOR;
expire -= ctx.random().nextLong(60*1000);
if (_pool.getSettings().isInbound())
expire -= ctx.random().nextLong(15*1000);
expire -= IB_EARLY_EXPIRE + ctx.random().nextLong(IB_EARLY_EXPIRE);
else
expire -= OB_EARLY_EXPIRE + ctx.random().nextLong(OB_EARLY_EXPIRE);
// See comments in TunnelPool.locked_buildNewLeaseSet
cfg.setExpiration(expire);
getTiming().setStartAfter(expire);
}
@ -42,6 +47,7 @@ class ExpireJob extends JobImpl {
if (!_leaseUpdated) {
_pool.removeTunnel(_cfg);
_leaseUpdated = true;
// noop for outbound
_pool.refreshLeaseSet();
long timeToDrop = _dropAfter - getContext().clock().now();
requeue(timeToDrop);

View File

@ -18,6 +18,7 @@ import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.router.tunnel.HopConfig;
import net.i2p.router.tunnel.TunnelCreatorConfig;
import net.i2p.stat.Rate;
import net.i2p.stat.RateAverages;
import net.i2p.stat.RateStat;
@ -725,7 +726,13 @@ public class TunnelPool {
continue;
}
Lease lease = new Lease();
lease.setEndDate(new Date(tunnel.getExpiration()));
// bugfix
// ExpireJob reduces the expiration, which causes a 2nd leaseset with the same lease
// to have an earlier expiration, so it isn't stored.
// Get the "real" expiration from the gateway hop config,
// HopConfig expirations are the same as the "real" expiration and don't change
// see configureNewTunnel()
lease.setEndDate(new Date(((TunnelCreatorConfig)tunnel).getConfig(0).getExpiration()));
lease.setTunnelId(inId);
lease.setGateway(gw);
leases.add(lease);
@ -1131,6 +1138,7 @@ public class TunnelPool {
// tunnelIds will be updated during building, and as the creator, we
// don't need to worry about prev/next hop
}
// note that this will be adjusted by expire job
cfg.setExpiration(expiration);
if (!settings.isInbound())
cfg.setPriority(settings.getPriority());