mirror of
https://github.com/i2p/i2p.i2p.git
synced 2024-12-06 19:27:00 +01:00
propagate from branch 'i2p.i2p' (head 60a9a2297abeaf042645e3f0bc8d106f1ff585bf)
to branch 'i2p.i2p.zzz.test2' (head 6ff6f0bcee835d32aad62449a37f5171afde915a)
This commit is contained in:
@@ -146,10 +146,10 @@ public class BuildRequestRecord {
|
||||
return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0;
|
||||
}
|
||||
/**
|
||||
* Time that the request was sent, truncated to the nearest hour
|
||||
* Time that the request was sent (ms), truncated to the nearest hour
|
||||
*/
|
||||
public long readRequestTime() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * 60l * 60l * 1000l;
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
|
||||
}
|
||||
/**
|
||||
* What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint,
|
||||
@@ -250,6 +250,8 @@ public class BuildRequestRecord {
|
||||
else if (isOutEndpoint)
|
||||
buf[OFF_FLAG] |= FLAG_OUTBOUND_ENDPOINT;
|
||||
long truncatedHour = ctx.clock().now();
|
||||
// prevent hop identification at top of the hour
|
||||
truncatedHour -= ctx.random().nextInt(90*1000);
|
||||
truncatedHour /= (60l*60l*1000l);
|
||||
DataHelper.toLong(buf, OFF_REQ_TIME, 4, truncatedHour);
|
||||
DataHelper.toLong(buf, OFF_SEND_MSG_ID, 4, nextMsgId);
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.Set;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
@@ -18,7 +18,7 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
/**
|
||||
* Defines a method of communicating with a router
|
||||
*
|
||||
* For efficiency, the options methods and structures here are unsynchronized.
|
||||
* Initialize the structure with readBytes(), or call the setOptions().
|
||||
* Don't change it after that.
|
||||
*
|
||||
* To ensure integrity of the RouterInfo, methods that change an element of the
|
||||
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
|
||||
*
|
||||
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
|
||||
* readin and the signature will fail.
|
||||
* If we implement expiration, or other use for the field, we must allow
|
||||
* several releases for the change to propagate as it is backwards-incompatible.
|
||||
* Restored as of 0.9.12.
|
||||
*
|
||||
* @since 0.9.16 moved from net.i2p.data
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterAddress extends DataStructureImpl {
|
||||
private short _cost;
|
||||
private long _expiration;
|
||||
private String _transportStyle;
|
||||
private final Properties _options;
|
||||
// cached values
|
||||
private byte[] _ip;
|
||||
private int _port;
|
||||
|
||||
public static final String PROP_HOST = "host";
|
||||
public static final String PROP_PORT = "port";
|
||||
|
||||
public RouterAddress() {
|
||||
_options = new OrderedProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* For efficiency when created by a Transport.
|
||||
* @param options not copied; do not reuse or modify
|
||||
* @param cost 0-255
|
||||
* @since IPv6
|
||||
*/
|
||||
public RouterAddress(String style, OrderedProperties options, int cost) {
|
||||
_transportStyle = style;
|
||||
_options = options;
|
||||
if (cost < 0 || cost > 255)
|
||||
throw new IllegalArgumentException();
|
||||
_cost = (short) cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the weighted cost of this address, relative to other methods of
|
||||
* contacting this router. The value 0 means free and 255 means really expensive.
|
||||
* No value above 255 is allowed.
|
||||
*
|
||||
* Unused before 0.7.12
|
||||
* @return 0-255
|
||||
*/
|
||||
public int getCost() {
|
||||
return _cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the weighted cost of using the address.
|
||||
* No value negative or above 255 is allowed.
|
||||
*
|
||||
* WARNING - do not change cost on a published address or it will break the RI sig.
|
||||
* There is no check here.
|
||||
* Rarely used, use 3-arg constructor.
|
||||
*
|
||||
* NTCP is set to 10 and SSU to 5 by default, unused before 0.7.12
|
||||
*/
|
||||
public void setCost(int cost) {
|
||||
if (cost < 0 || cost > 255)
|
||||
throw new IllegalArgumentException();
|
||||
_cost = (short) cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the date after which the address should not be used. If this
|
||||
* is null, then the address never expires.
|
||||
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
|
||||
* readin and the signature will fail.
|
||||
* Restored as of 0.9.12.
|
||||
*
|
||||
* @deprecated unused for now
|
||||
* @return null for never, or a Date
|
||||
*/
|
||||
public Date getExpiration() {
|
||||
//return _expiration;
|
||||
if (_expiration > 0)
|
||||
return new Date(_expiration);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the date after which the address should not be used. If this
|
||||
* is zero, then the address never expires.
|
||||
*
|
||||
* @deprecated unused for now
|
||||
* @return 0 for never
|
||||
* @since 0.9.12
|
||||
*/
|
||||
public long getExpirationTime() {
|
||||
return _expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the expiration date of the address (null for no expiration)
|
||||
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
|
||||
* readin and the signature will fail.
|
||||
* Restored as of 0.9.12, wait several more releases before using.
|
||||
* TODO: Use for introducers
|
||||
*
|
||||
* Unused for now, always null
|
||||
* @deprecated unused for now
|
||||
*/
|
||||
public void setExpiration(Date expiration) {
|
||||
_expiration = expiration.getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of transport that must be used to communicate on this address.
|
||||
*
|
||||
*/
|
||||
public String getTransportStyle() {
|
||||
return _transportStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the type of transport that must be used to communicate on this address
|
||||
*
|
||||
* @throws IllegalStateException if was already set
|
||||
* @deprecated unused, use 3-arg constructor
|
||||
*/
|
||||
public void setTransportStyle(String transportStyle) {
|
||||
if (_transportStyle != null)
|
||||
throw new IllegalStateException();
|
||||
_transportStyle = transportStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the transport specific options necessary for communication
|
||||
*
|
||||
* @deprecated use getOptionsMap()
|
||||
* @return sorted, non-null, NOT a copy, do not modify
|
||||
*/
|
||||
public Properties getOptions() {
|
||||
return _options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the transport specific options necessary for communication
|
||||
*
|
||||
* @return an unmodifiable view, non-null, sorted
|
||||
* @since 0.8.13
|
||||
*/
|
||||
public Map<Object, Object> getOptionsMap() {
|
||||
return Collections.unmodifiableMap(_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.13
|
||||
*/
|
||||
public String getOption(String opt) {
|
||||
return _options.getProperty(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the transport specific options necessary for communication.
|
||||
* Makes a copy.
|
||||
* @param options non-null
|
||||
* @throws IllegalStateException if was already set
|
||||
* @deprecated unused, use 3-arg constructor
|
||||
*/
|
||||
public void setOptions(Properties options) {
|
||||
if (!_options.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
_options.putAll(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caching version of InetAddress.getByName(getOption("host")).getAddress(), which is slow.
|
||||
* Caches numeric host names only.
|
||||
* Will resolve but not cache resolution of DNS host names.
|
||||
*
|
||||
* @return IP or null
|
||||
* @since 0.9.3
|
||||
*/
|
||||
public byte[] getIP() {
|
||||
if (_ip != null)
|
||||
return _ip;
|
||||
byte[] rv = null;
|
||||
String host = getHost();
|
||||
if (host != null) {
|
||||
rv = Addresses.getIP(host);
|
||||
if (rv != null &&
|
||||
(host.replaceAll("[0-9\\.]", "").length() == 0 ||
|
||||
host.replaceAll("[0-9a-fA-F:]", "").length() == 0)) {
|
||||
_ip = rv;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience, same as getOption("host").
|
||||
* Does no parsing, so faster than getIP().
|
||||
*
|
||||
* @return host string or null
|
||||
* @since IPv6
|
||||
*/
|
||||
public String getHost() {
|
||||
return _options.getProperty(PROP_HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caching version of Integer.parseInt(getOption("port"))
|
||||
* Caches valid ports 1-65535 only.
|
||||
*
|
||||
* @return 1-65535 or 0 if invalid
|
||||
* @since 0.9.3
|
||||
*/
|
||||
public int getPort() {
|
||||
if (_port != 0)
|
||||
return _port;
|
||||
String port = _options.getProperty(PROP_PORT);
|
||||
if (port != null) {
|
||||
try {
|
||||
int rv = Integer.parseInt(port);
|
||||
if (rv > 0 && rv <= 65535)
|
||||
_port = rv;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return _port;
|
||||
}
|
||||
|
||||
/**
|
||||
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
|
||||
* readin and the signature will fail.
|
||||
* Restored as of 0.9.12, wait several more releases before using.
|
||||
* @throws IllegalStateException if was already read in
|
||||
*/
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
if (_transportStyle != null)
|
||||
throw new IllegalStateException();
|
||||
_cost = (short) DataHelper.readLong(in, 1);
|
||||
_expiration = DataHelper.readLong(in, 8);
|
||||
_transportStyle = DataHelper.readString(in);
|
||||
// reduce Object proliferation
|
||||
if (_transportStyle.equals("SSU"))
|
||||
_transportStyle = "SSU";
|
||||
else if (_transportStyle.equals("NTCP"))
|
||||
_transportStyle = "NTCP";
|
||||
DataHelper.readProperties(in, _options);
|
||||
}
|
||||
|
||||
/**
|
||||
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
|
||||
* readin and the signature will fail.
|
||||
*/
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_transportStyle == null)
|
||||
throw new DataFormatException("uninitialized");
|
||||
DataHelper.writeLong(out, 1, _cost);
|
||||
DataHelper.writeLong(out, 8, _expiration);
|
||||
DataHelper.writeString(out, _transportStyle);
|
||||
DataHelper.writeProperties(out, _options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport, host, and port only.
|
||||
* Never look at cost or other properties.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == this) return true;
|
||||
if ((object == null) || !(object instanceof RouterAddress)) return false;
|
||||
RouterAddress addr = (RouterAddress) object;
|
||||
return
|
||||
getPort() == addr.getPort() &&
|
||||
DataHelper.eq(getHost(), addr.getHost()) &&
|
||||
DataHelper.eq(_transportStyle, addr._transportStyle);
|
||||
//DataHelper.eq(_options, addr._options) &&
|
||||
//DataHelper.eq(_expiration, addr._expiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything, including Transport, host, port, options, and cost
|
||||
* @param addr may be null
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean deepEquals(RouterAddress addr) {
|
||||
return
|
||||
equals(addr) &&
|
||||
_cost == addr._cost &&
|
||||
_options.equals(addr._options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just use a few items for speed (expiration is always null).
|
||||
* Never look at cost or other properties.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(_transportStyle) ^
|
||||
DataHelper.hashCode(getIP()) ^
|
||||
getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used on peers.jsp so sort options so it looks better.
|
||||
* We don't just use OrderedProperties for _options because DataHelper.writeProperties()
|
||||
* sorts also.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
buf.append("[RouterAddress: ");
|
||||
buf.append("\n\tType: ").append(_transportStyle);
|
||||
buf.append("\n\tCost: ").append(_cost);
|
||||
if (_expiration > 0)
|
||||
buf.append("\n\tExpiration: ").append(new Date(_expiration));
|
||||
buf.append("\n\tOptions (").append(_options.size()).append("):");
|
||||
for (Map.Entry<Object, Object> e : _options.entrySet()) {
|
||||
String key = (String) e.getKey();
|
||||
String val = (String) e.getValue();
|
||||
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
|
||||
}
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.KeysAndCert;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the unique identifier of a router, including any certificate or
|
||||
* public key.
|
||||
*
|
||||
* As of 0.9.9 this data structure is immutable after the two keys and the certificate
|
||||
* are set; attempts to change them will throw an IllegalStateException.
|
||||
*
|
||||
* @since 0.9.16 moved from net.i2p.data
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterIdentity extends KeysAndCert {
|
||||
|
||||
/**
|
||||
* This router specified that they should not be used as a part of a tunnel,
|
||||
* nor queried for the netDb, and that disclosure of their contact information
|
||||
* should be limited.
|
||||
*
|
||||
*/
|
||||
public boolean isHidden() {
|
||||
return (_certificate != null) && (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_HIDDEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o) && (o instanceof RouterIdentity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
/*
|
||||
* 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.io.OutputStream;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.crypto.SHA1Hash;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.KeysAndCert;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Defines the data that a router either publishes to the global routing table or
|
||||
* provides to trusted peers.
|
||||
*
|
||||
* For efficiency, the methods and structures here are now unsynchronized.
|
||||
* Initialize the RI with readBytes(), or call the setters and then sign() in a single thread.
|
||||
* Don't change it after that.
|
||||
*
|
||||
* To ensure integrity of the RouterInfo, methods that change an element of the
|
||||
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
|
||||
*
|
||||
* @since 0.9.16 moved from net.i2p.data
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterInfo extends DatabaseEntry {
|
||||
private RouterIdentity _identity;
|
||||
private volatile long _published;
|
||||
/**
|
||||
* Addresses must be sorted by SHA256.
|
||||
* When an RI is created, they are sorted in setAddresses().
|
||||
* Save addresses in the order received so we need not resort.
|
||||
*/
|
||||
private final List<RouterAddress> _addresses;
|
||||
/** may be null to save memory, no longer final */
|
||||
private Set<Hash> _peers;
|
||||
private final Properties _options;
|
||||
private volatile boolean _validated;
|
||||
private volatile boolean _isValid;
|
||||
//private volatile String _stringified;
|
||||
private volatile byte _byteified[];
|
||||
private volatile int _hashCode;
|
||||
private volatile boolean _hashCodeInitialized;
|
||||
/** should we cache the byte and string versions _byteified ? **/
|
||||
private boolean _shouldCache;
|
||||
/** maybe we should check if we are floodfill? */
|
||||
private static final boolean CACHE_ALL = SystemVersion.getMaxMemory() > 128*1024*1024l;
|
||||
|
||||
public static final String PROP_NETWORK_ID = "netId";
|
||||
public static final String PROP_CAPABILITIES = "caps";
|
||||
public static final char CAPABILITY_HIDDEN = 'H';
|
||||
|
||||
// Public string of chars which serve as bandwidth capacity markers
|
||||
// NOTE: individual chars defined in Router.java
|
||||
public static final String BW_CAPABILITY_CHARS = "KLMNO";
|
||||
|
||||
public RouterInfo() {
|
||||
_addresses = new ArrayList<RouterAddress>(2);
|
||||
_options = new OrderedProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used only by Router and PublishLocalRouterInfoJob.
|
||||
* Copies ONLY the identity and peers.
|
||||
* Does not copy published, addresses, options, or signature.
|
||||
*/
|
||||
public RouterInfo(RouterInfo old) {
|
||||
this();
|
||||
setIdentity(old.getIdentity());
|
||||
//setPublished(old.getPublished());
|
||||
//setAddresses(old.getAddresses());
|
||||
setPeers(old.getPeers());
|
||||
//setOptions(old.getOptions());
|
||||
//setSignature(old.getSignature());
|
||||
// copy over _byteified?
|
||||
}
|
||||
|
||||
public long getDate() {
|
||||
return _published;
|
||||
}
|
||||
|
||||
protected KeysAndCert getKeysAndCert() {
|
||||
return _identity;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return KEY_TYPE_ROUTERINFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the identity of the router represented
|
||||
*
|
||||
*/
|
||||
public RouterIdentity getIdentity() {
|
||||
return _identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the identity of the router represented
|
||||
*
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void setIdentity(RouterIdentity ident) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
_identity = ident;
|
||||
// We only want to cache the bytes for our own RI, which is frequently written.
|
||||
// To cache for all RIs doubles the RI memory usage.
|
||||
// setIdentity() is only called when we are creating our own RI.
|
||||
// Otherwise, the data is populated with readBytes().
|
||||
_shouldCache = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the approximate date on which the info was published
|
||||
* (essentially a version number for the routerInfo structure, except that
|
||||
* it also contains freshness information - whether or not the router is
|
||||
* currently publishing its information). This should be used to help expire
|
||||
* old routerInfo structures
|
||||
*
|
||||
*/
|
||||
public long getPublished() {
|
||||
return _published;
|
||||
}
|
||||
|
||||
/**
|
||||
* Date on which it was published, in milliseconds since Midnight GMT on Jan 01, 1970
|
||||
*
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void setPublished(long published) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
_published = published;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the set of RouterAddress structures at which this
|
||||
* router can be contacted.
|
||||
*
|
||||
* @return unmodifiable view, non-null
|
||||
*/
|
||||
public Collection<RouterAddress> getAddresses() {
|
||||
return Collections.unmodifiableCollection(_addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a set of RouterAddress structures at which this router
|
||||
* can be contacted.
|
||||
*
|
||||
* Warning - Sorts the addresses here. Do not modify any address
|
||||
* after calling this, as the sort order is based on the
|
||||
* hash of the entire address structure.
|
||||
*
|
||||
* @param addresses may be null
|
||||
* @throws IllegalStateException if RouterInfo is already signed or addresses previously set
|
||||
*/
|
||||
public void setAddresses(Collection<RouterAddress> addresses) {
|
||||
if (_signature != null || !_addresses.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
if (addresses != null) {
|
||||
_addresses.addAll(addresses);
|
||||
if (_addresses.size() > 1) {
|
||||
// WARNING this sort algorithm cannot be changed, as it must be consistent
|
||||
// network-wide. The signature is not checked at readin time, but only
|
||||
// later, and the addresses are stored in a Set, not a List.
|
||||
SortHelper.sortStructureList(_addresses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of SHA-256 hashes of RouterIdentities from routers
|
||||
* this router can be reached through.
|
||||
*
|
||||
* @deprecated Implemented here but unused elsewhere
|
||||
*/
|
||||
public Set<Hash> getPeers() {
|
||||
if (_peers == null)
|
||||
return Collections.emptySet();
|
||||
return _peers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a set of SHA-256 hashes of RouterIdentities from routers
|
||||
* this router can be reached through.
|
||||
*
|
||||
* @deprecated Implemented here but unused elsewhere
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void setPeers(Set<Hash> peers) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
if (peers == null || peers.isEmpty()) {
|
||||
_peers = null;
|
||||
return;
|
||||
}
|
||||
if (_peers == null)
|
||||
_peers = new HashSet<Hash>(2);
|
||||
synchronized (_peers) {
|
||||
_peers.clear();
|
||||
_peers.addAll(peers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of options or statistics that the router can expose.
|
||||
*
|
||||
* @deprecated use getOptionsMap()
|
||||
* @return sorted, non-null, NOT a copy, do not modify!!!
|
||||
*/
|
||||
public Properties getOptions() {
|
||||
return _options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of options or statistics that the router can expose.
|
||||
*
|
||||
* @return an unmodifiable view, non-null, sorted
|
||||
* @since 0.8.13
|
||||
*/
|
||||
public Map<Object, Object> getOptionsMap() {
|
||||
return Collections.unmodifiableMap(_options);
|
||||
}
|
||||
|
||||
public String getOption(String opt) {
|
||||
return _options.getProperty(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a set of options or statistics that the router can expose.
|
||||
* Makes a copy.
|
||||
*
|
||||
* @param options if null, clears current options
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void setOptions(Properties options) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
_options.clear();
|
||||
if (options != null)
|
||||
_options.putAll(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the raw payload of the routerInfo, excluding the signature. This
|
||||
* caches the data in memory if possible.
|
||||
*
|
||||
* @throws DataFormatException if the data is somehow b0rked (missing props, etc)
|
||||
*/
|
||||
protected byte[] getBytes() throws DataFormatException {
|
||||
if (_byteified != null) return _byteified;
|
||||
if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!");
|
||||
|
||||
//long before = Clock.getInstance().now();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(2*1024);
|
||||
try {
|
||||
_identity.writeBytes(out);
|
||||
// avoid thrashing objects
|
||||
//DataHelper.writeDate(out, new Date(_published));
|
||||
DataHelper.writeLong(out, 8, _published);
|
||||
int sz = _addresses.size();
|
||||
if (sz <= 0 || isHidden()) {
|
||||
// Do not send IP address to peers in hidden mode
|
||||
DataHelper.writeLong(out, 1, 0);
|
||||
} else {
|
||||
DataHelper.writeLong(out, 1, sz);
|
||||
for (RouterAddress addr : _addresses) {
|
||||
addr.writeBytes(out);
|
||||
}
|
||||
}
|
||||
// XXX: what about peers?
|
||||
// answer: they're always empty... they're a placeholder for one particular
|
||||
// method of trusted links, which isn't implemented in the router
|
||||
// at the moment, and may not be later.
|
||||
int psz = _peers == null ? 0 : _peers.size();
|
||||
DataHelper.writeLong(out, 1, psz);
|
||||
if (psz > 0) {
|
||||
Collection<Hash> peers = _peers;
|
||||
if (psz > 1)
|
||||
// WARNING this sort algorithm cannot be changed, as it must be consistent
|
||||
// network-wide. The signature is not checked at readin time, but only
|
||||
// later, and the hashes are stored in a Set, not a List.
|
||||
peers = (Collection<Hash>) SortHelper.sortStructures(peers);
|
||||
for (Hash peerHash : peers) {
|
||||
peerHash.writeBytes(out);
|
||||
}
|
||||
}
|
||||
DataHelper.writeProperties(out, _options);
|
||||
} catch (IOException ioe) {
|
||||
throw new DataFormatException("IO Error getting bytes", ioe);
|
||||
}
|
||||
byte data[] = out.toByteArray();
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
// long after = Clock.getInstance().now();
|
||||
// _log.debug("getBytes() took " + (after - before) + "ms");
|
||||
//}
|
||||
if (CACHE_ALL || _shouldCache)
|
||||
_byteified = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this router info is authorized with a valid signature
|
||||
*
|
||||
*/
|
||||
public boolean isValid() {
|
||||
if (!_validated) doValidate();
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as isValid()
|
||||
* @since 0.9
|
||||
*/
|
||||
@Override
|
||||
public boolean verifySignature() {
|
||||
return isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* which network is this routerInfo a part of. configured through the property
|
||||
* PROP_NETWORK_ID
|
||||
* @return -1 if unknown
|
||||
*/
|
||||
public int getNetworkId() {
|
||||
String id = _options.getProperty(PROP_NETWORK_ID);
|
||||
if (id != null) {
|
||||
try {
|
||||
return Integer.parseInt(id);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* what special capabilities this router offers
|
||||
* @return non-null, empty string if none
|
||||
*/
|
||||
public String getCapabilities() {
|
||||
String capabilities = _options.getProperty(PROP_CAPABILITIES);
|
||||
if (capabilities != null)
|
||||
return capabilities;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a hidden node?
|
||||
*/
|
||||
public boolean isHidden() {
|
||||
return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of this node's bandwidth tier,
|
||||
* or "Unknown"
|
||||
*/
|
||||
public String getBandwidthTier() {
|
||||
String bwTiers = BW_CAPABILITY_CHARS;
|
||||
String bwTier = "Unknown";
|
||||
String capabilities = getCapabilities();
|
||||
// Iterate through capabilities, searching for known bandwidth tier
|
||||
for (int i = 0; i < capabilities.length(); i++) {
|
||||
if (bwTiers.indexOf(String.valueOf(capabilities.charAt(i))) != -1) {
|
||||
bwTier = String.valueOf(capabilities.charAt(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (bwTier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void addCapability(char cap) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
String caps = _options.getProperty(PROP_CAPABILITIES);
|
||||
if (caps == null)
|
||||
_options.setProperty(PROP_CAPABILITIES, ""+cap);
|
||||
else if (caps.indexOf(cap) == -1)
|
||||
_options.setProperty(PROP_CAPABILITIES, caps + cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void delCapability(char cap) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
String caps = _options.getProperty(PROP_CAPABILITIES);
|
||||
int idx;
|
||||
if (caps == null) {
|
||||
return;
|
||||
} else if ((idx = caps.indexOf(cap)) == -1) {
|
||||
return;
|
||||
} else {
|
||||
StringBuilder buf = new StringBuilder(caps);
|
||||
while ( (idx = buf.indexOf(""+cap)) != -1)
|
||||
buf.deleteCharAt(idx);
|
||||
_options.setProperty(PROP_CAPABILITIES, buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the router was published recently (within the given age milliseconds).
|
||||
* The age should be large enough to take into consideration any clock fudge factor, so
|
||||
* values such as 1 or 2 hours are probably reasonable.
|
||||
*
|
||||
* @param maxAgeMs milliseconds between the current time and publish date to check
|
||||
* @return true if it was published recently, false otherwise
|
||||
*/
|
||||
public boolean isCurrent(long maxAgeMs) {
|
||||
long earliestExpire = Clock.getInstance().now() - maxAgeMs;
|
||||
if (_published < earliestExpire)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pull the first workable target address for the given transport.
|
||||
* Use to check for any address. For all addresses, use getTargetAddresses(),
|
||||
* which you probably want if you care about IPv6.
|
||||
*/
|
||||
public RouterAddress getTargetAddress(String transportStyle) {
|
||||
for (RouterAddress addr : _addresses) {
|
||||
if (addr.getTransportStyle().equals(transportStyle))
|
||||
return addr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For multiple addresses per-transport (IPv4 or IPv6)
|
||||
* @return non-null
|
||||
* @since 0.7.11
|
||||
*/
|
||||
public List<RouterAddress> getTargetAddresses(String transportStyle) {
|
||||
List<RouterAddress> ret = new ArrayList<RouterAddress>(_addresses.size());
|
||||
for (RouterAddress addr : _addresses) {
|
||||
if(addr.getTransportStyle().equals(transportStyle))
|
||||
ret.add(addr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually validate the signature
|
||||
*/
|
||||
private void doValidate() {
|
||||
_isValid = super.verifySignature();
|
||||
_validated = true;
|
||||
|
||||
if (!_isValid) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(RouterInfo.class);
|
||||
byte data[] = null;
|
||||
try {
|
||||
data = getBytes();
|
||||
} catch (DataFormatException dfe) {
|
||||
log.error("Error validating", dfe);
|
||||
return;
|
||||
}
|
||||
log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
|
||||
+ (log.shouldLog(Log.WARN) ? ("]\n" + toString()) : ""),
|
||||
new Exception("Signature failed"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This does NOT validate the signature
|
||||
*
|
||||
* @throws IllegalStateException if RouterInfo was already read in
|
||||
*/
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
readBytes(in, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* If verifySig is true,
|
||||
* this validates the signature while reading in,
|
||||
* and throws a DataFormatException if the sig is invalid.
|
||||
* This is faster than reserializing to validate later.
|
||||
*
|
||||
* @throws IllegalStateException if RouterInfo was already read in
|
||||
* @since 0.9
|
||||
*/
|
||||
public void readBytes(InputStream in, boolean verifySig) throws DataFormatException, IOException {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
_identity = new RouterIdentity();
|
||||
_identity.readBytes(in);
|
||||
// can't set the digest until we know the sig type
|
||||
InputStream din;
|
||||
MessageDigest digest;
|
||||
if (verifySig) {
|
||||
SigType type = _identity.getSigningPublicKey().getType();
|
||||
if (type != SigType.EdDSA_SHA512_Ed25519) {
|
||||
// This won't work for EdDSA
|
||||
digest = _identity.getSigningPublicKey().getType().getDigestInstance();
|
||||
// TODO any better way?
|
||||
digest.update(_identity.toByteArray());
|
||||
din = new DigestInputStream(in, digest);
|
||||
} else {
|
||||
digest = null;
|
||||
din = in;
|
||||
}
|
||||
} else {
|
||||
digest = null;
|
||||
din = in;
|
||||
}
|
||||
// avoid thrashing objects
|
||||
//Date when = DataHelper.readDate(in);
|
||||
//if (when == null)
|
||||
// _published = 0;
|
||||
//else
|
||||
// _published = when.getTime();
|
||||
_published = DataHelper.readLong(din, 8);
|
||||
int numAddresses = (int) DataHelper.readLong(din, 1);
|
||||
for (int i = 0; i < numAddresses; i++) {
|
||||
RouterAddress address = new RouterAddress();
|
||||
address.readBytes(din);
|
||||
_addresses.add(address);
|
||||
}
|
||||
int numPeers = (int) DataHelper.readLong(din, 1);
|
||||
if (numPeers == 0) {
|
||||
_peers = null;
|
||||
} else {
|
||||
_peers = new HashSet<Hash>(numPeers);
|
||||
for (int i = 0; i < numPeers; i++) {
|
||||
Hash peerIdentityHash = new Hash();
|
||||
peerIdentityHash.readBytes(din);
|
||||
_peers.add(peerIdentityHash);
|
||||
}
|
||||
}
|
||||
DataHelper.readProperties(din, _options);
|
||||
_signature = new Signature(_identity.getSigningPublicKey().getType());
|
||||
_signature.readBytes(in);
|
||||
|
||||
if (verifySig) {
|
||||
SigType type = _identity.getSigningPublicKey().getType();
|
||||
if (type != SigType.EdDSA_SHA512_Ed25519) {
|
||||
// This won't work for EdDSA
|
||||
SimpleDataStructure hash = _identity.getSigningPublicKey().getType().getHashInstance();
|
||||
hash.setData(digest.digest());
|
||||
_isValid = DSAEngine.getInstance().verifySignature(_signature, hash, _identity.getSigningPublicKey());
|
||||
_validated = true;
|
||||
} else {
|
||||
doValidate();
|
||||
}
|
||||
if (!_isValid) {
|
||||
throw new DataFormatException("Bad sig");
|
||||
}
|
||||
}
|
||||
|
||||
//_log.debug("Read routerInfo: " + toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* This does NOT validate the signature
|
||||
*/
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_identity == null) throw new DataFormatException("Missing identity");
|
||||
if (_published < 0) throw new DataFormatException("Invalid published date: " + _published);
|
||||
if (_signature == null) throw new DataFormatException("Signature is null");
|
||||
//if (!isValid())
|
||||
// throw new DataFormatException("Data is not valid");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
|
||||
baos.write(getBytes());
|
||||
_signature.writeBytes(baos);
|
||||
|
||||
byte data[] = baos.toByteArray();
|
||||
//_log.debug("Writing routerInfo [len=" + data.length + "]: " + toString());
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == this) return true;
|
||||
if ((object == null) || !(object instanceof RouterInfo)) return false;
|
||||
RouterInfo info = (RouterInfo) object;
|
||||
return
|
||||
_published == info.getPublished()
|
||||
&& DataHelper.eq(_signature, info.getSignature())
|
||||
&& DataHelper.eq(_identity, info.getIdentity());
|
||||
// Let's speed up the NetDB
|
||||
//&& DataHelper.eq(_addresses, info.getAddresses())
|
||||
//&& DataHelper.eq(_options, info.getOptions())
|
||||
//&& DataHelper.eq(getPeers(), info.getPeers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (!_hashCodeInitialized) {
|
||||
_hashCode = DataHelper.hashCode(_identity) + (int) _published;
|
||||
_hashCodeInitialized = true;
|
||||
}
|
||||
return _hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
//if (_stringified != null) return _stringified;
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("[RouterInfo: ");
|
||||
buf.append("\n\tIdentity: ").append(_identity);
|
||||
buf.append("\n\tSignature: ").append(_signature);
|
||||
buf.append("\n\tPublished: ").append(new Date(_published));
|
||||
if (_peers != null) {
|
||||
buf.append("\n\tPeers (").append(_peers.size()).append("):");
|
||||
for (Hash hash : _peers) {
|
||||
buf.append("\n\t\tPeer hash: ").append(hash);
|
||||
}
|
||||
}
|
||||
buf.append("\n\tOptions (").append(_options.size()).append("):");
|
||||
for (Map.Entry<Object, Object> e : _options.entrySet()) {
|
||||
String key = (String) e.getKey();
|
||||
String val = (String) e.getValue();
|
||||
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
|
||||
}
|
||||
if (!_addresses.isEmpty()) {
|
||||
buf.append("\n\tAddresses (").append(_addresses.size()).append("):");
|
||||
for (RouterAddress addr : _addresses) {
|
||||
buf.append("\n\t").append(addr);
|
||||
}
|
||||
}
|
||||
buf.append("]");
|
||||
String rv = buf.toString();
|
||||
//_stringified = rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print out routerinfos from files specified on the command line.
|
||||
* Exits 1 if any RI is invalid, fails signature, etc.
|
||||
* @since 0.8
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length <= 0) {
|
||||
System.err.println("Usage: RouterInfo file ...");
|
||||
System.exit(1);
|
||||
}
|
||||
boolean fail = false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
RouterInfo ri = new RouterInfo();
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new java.io.FileInputStream(args[i]);
|
||||
ri.readBytes(is);
|
||||
if (ri.isValid()) {
|
||||
System.out.println(ri.toString());
|
||||
} else {
|
||||
System.err.println("Router info " + args[i] + " is invalid");
|
||||
fail = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error reading " + args[i] + ": " + e);
|
||||
fail = true;
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try { is.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail)
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
/**
|
||||
* Same format as super, simply adds a method to
|
||||
* treat it as a RouterIdentity instead of a Destination.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public class RouterPrivateKeyFile extends PrivateKeyFile {
|
||||
|
||||
public RouterPrivateKeyFile(File file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read it in from the file.
|
||||
* Also sets the local privKey and signingPrivKey.
|
||||
*/
|
||||
public RouterIdentity getRouterIdentity() throws IOException, DataFormatException {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new BufferedInputStream(new FileInputStream(this.file));
|
||||
RouterIdentity ri = new RouterIdentity();
|
||||
ri.readBytes(in);
|
||||
privKey = new PrivateKey();
|
||||
privKey.readBytes(in);
|
||||
SigType type = ri.getSigningPublicKey().getType();
|
||||
if (type == null)
|
||||
throw new DataFormatException("Unknown sig type");
|
||||
signingPrivKey = new SigningPrivateKey(type);
|
||||
signingPrivKey.readBytes(in);
|
||||
|
||||
// set it a Destination, so we may call validateKeyPairs()
|
||||
// or other methods
|
||||
dest = new Destination();
|
||||
dest.setPublicKey(ri.getPublicKey());
|
||||
dest.setSigningPublicKey(ri.getSigningPublicKey());
|
||||
dest.setCertificate(ri.getCertificate());
|
||||
dest.setPadding(ri.getPadding());
|
||||
|
||||
return ri;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataStructure;
|
||||
|
||||
/**
|
||||
* The sorting of addresses in RIs
|
||||
*
|
||||
* @since 0.9.16 moved from DataHelper
|
||||
*/
|
||||
class SortHelper {
|
||||
|
||||
/**
|
||||
* Sort based on the Hash of the DataStructure.
|
||||
* Warning - relatively slow.
|
||||
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
|
||||
* it cannot be changed.
|
||||
* Why? Just because it has to be consistent so signing will work.
|
||||
* How to spec as returning the same type as the param?
|
||||
* DEPRECATED - Only used by RouterInfo.
|
||||
*
|
||||
* @return a new list
|
||||
*/
|
||||
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
|
||||
if (dataStructures == null) return Collections.emptyList();
|
||||
|
||||
// This used to use Hash.toString(), which is insane, since a change to toString()
|
||||
// would break the whole network. Now use Hash.toBase64().
|
||||
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
|
||||
// despite what you may read elsewhere.
|
||||
|
||||
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
|
||||
//TreeMap<String, DataStructure> tm = new TreeMap();
|
||||
//for (DataStructure struct : dataStructures) {
|
||||
// tm.put(struct.calculateHash().toString(), struct);
|
||||
//}
|
||||
//for (DataStructure struct : tm.values()) {
|
||||
// rv.add(struct);
|
||||
//}
|
||||
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
|
||||
sortStructureList(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* See above.
|
||||
* DEPRECATED - Only used by RouterInfo.
|
||||
*
|
||||
* @since 0.9
|
||||
*/
|
||||
static void sortStructureList(List<? extends DataStructure> dataStructures) {
|
||||
Collections.sort(dataStructures, new DataStructureComparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* See sortStructures() comments.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
|
||||
public int compare(DataStructure l, DataStructure r) {
|
||||
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Classes formerly in net.i2p.data but moved here as they are only used by the router.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -28,8 +28,8 @@ import java.util.TreeSet;
|
||||
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.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.io.Writer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
|
||||
/**
|
||||
* Manages the communication subsystem between peers, including connections,
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.io.OutputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataStructure;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -26,6 +27,7 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.router.startup.CreateRouterInfoJob;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
@@ -47,10 +49,10 @@ public class KeyManager {
|
||||
|
||||
public final static String PROP_KEYDIR = "router.keyBackupDir";
|
||||
public final static String DEFAULT_KEYDIR = "keyBackup";
|
||||
private final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
|
||||
private final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
|
||||
private final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
|
||||
private final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
|
||||
public final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
|
||||
public final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
|
||||
public final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
|
||||
public final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
|
||||
|
||||
public KeyManager(RouterContext context) {
|
||||
_context = context;
|
||||
@@ -151,8 +153,9 @@ public class KeyManager {
|
||||
private void syncKeys(File keyDir) {
|
||||
syncPrivateKey(keyDir);
|
||||
syncPublicKey(keyDir);
|
||||
syncSigningKey(keyDir);
|
||||
syncVerificationKey(keyDir);
|
||||
SigType type = CreateRouterInfoJob.getSigTypeConfig(getContext());
|
||||
syncSigningKey(keyDir, type);
|
||||
syncVerificationKey(keyDir, type);
|
||||
}
|
||||
|
||||
private void syncPrivateKey(File keyDir) {
|
||||
@@ -181,27 +184,33 @@ public class KeyManager {
|
||||
_publicKey = (PublicKey) readin;
|
||||
}
|
||||
|
||||
private void syncSigningKey(File keyDir) {
|
||||
/**
|
||||
* @param type the SigType to expect on read-in, ignored on write
|
||||
*/
|
||||
private void syncSigningKey(File keyDir, SigType type) {
|
||||
DataStructure ds;
|
||||
File keyFile = new File(keyDir, KEYFILE_PRIVATE_SIGNING);
|
||||
boolean exists = (_signingPrivateKey != null);
|
||||
if (exists)
|
||||
ds = _signingPrivateKey;
|
||||
else
|
||||
ds = new SigningPrivateKey();
|
||||
ds = new SigningPrivateKey(type);
|
||||
DataStructure readin = syncKey(keyFile, ds, exists);
|
||||
if (readin != null && !exists)
|
||||
_signingPrivateKey = (SigningPrivateKey) readin;
|
||||
}
|
||||
|
||||
private void syncVerificationKey(File keyDir) {
|
||||
/**
|
||||
* @param type the SigType to expect on read-in, ignored on write
|
||||
*/
|
||||
private void syncVerificationKey(File keyDir, SigType type) {
|
||||
DataStructure ds;
|
||||
File keyFile = new File(keyDir, KEYFILE_PUBLIC_SIGNING);
|
||||
boolean exists = (_signingPublicKey != null);
|
||||
if (exists)
|
||||
ds = _signingPublicKey;
|
||||
else
|
||||
ds = new SigningPublicKey();
|
||||
ds = new SigningPublicKey(type);
|
||||
DataStructure readin = syncKey(keyFile, ds, exists);
|
||||
if (readin != null && !exists)
|
||||
_signingPublicKey = (SigningPublicKey) readin;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class LeaseSetKeys {
|
||||
/**
|
||||
* Key with which a LeaseSet can be revoked (by republishing it with no Leases)
|
||||
*
|
||||
* @deprecated unused
|
||||
* Deprecated, unused
|
||||
*/
|
||||
public SigningPrivateKey getRevocationKey() { return _revocationKey; }
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Scanner;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,9 +14,10 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.networkdb.reseed.ReseedChecker;
|
||||
|
||||
/**
|
||||
@@ -51,18 +52,51 @@ public abstract class NetworkDatabaseFacade implements Service {
|
||||
public abstract LeaseSet lookupLeaseSetLocally(Hash key);
|
||||
public abstract void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs);
|
||||
public abstract RouterInfo lookupRouterInfoLocally(Hash key);
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Succeeds even if LS validation fails due to unsupported sig type
|
||||
*
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public abstract void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest);
|
||||
|
||||
/**
|
||||
* Lookup locally in netDB and in badDest cache
|
||||
* Succeeds even if LS validation failed due to unsupported sig type
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public abstract Destination lookupDestinationLocally(Hash key);
|
||||
|
||||
/**
|
||||
* return the leaseSet if another leaseSet already existed at that key
|
||||
* @return the leaseSet if another leaseSet already existed at that key
|
||||
*
|
||||
* @throws IllegalArgumentException if the data is not valid
|
||||
*/
|
||||
public abstract LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* return the routerInfo if another router already existed at that key
|
||||
* @return the routerInfo if another router already existed at that key
|
||||
*
|
||||
* @throws IllegalArgumentException if the data is not valid
|
||||
*/
|
||||
public abstract RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @return the old entry if it already existed at that key
|
||||
* @throws IllegalArgumentException if the data is not valid
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public DatabaseEntry store(Hash key, DatabaseEntry entry) throws IllegalArgumentException {
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO)
|
||||
return store(key, (RouterInfo) entry);
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET)
|
||||
return store(key, (LeaseSet) entry);
|
||||
throw new IllegalArgumentException("unknown type");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the local router is not valid
|
||||
*/
|
||||
@@ -101,4 +135,12 @@ public abstract class NetworkDatabaseFacade implements Service {
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean floodfillEnabled() { return false; };
|
||||
|
||||
/**
|
||||
* Is it permanently negative cached?
|
||||
*
|
||||
* @param key only for Destinations; for RouterIdentities, see Banlist
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public boolean isNegativeCachedForever(Hash key) { return false; }
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.util.CDPQEntry;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -70,9 +70,8 @@ public class PersistentKeyRing extends KeyRing {
|
||||
Hash h = e.getKey();
|
||||
buf.append(h.toBase64().substring(0, 6)).append("…");
|
||||
buf.append("<td>");
|
||||
LeaseSet ls = _ctx.netDb().lookupLeaseSetLocally(h);
|
||||
if (ls != null) {
|
||||
Destination dest = ls.getDestination();
|
||||
Destination dest = _ctx.netDb().lookupDestinationLocally(h);
|
||||
if (dest != null) {
|
||||
if (_ctx.clientManager().isLocal(dest)) {
|
||||
TunnelPoolSettings in = _ctx.tunnelManager().getInboundSettings(h);
|
||||
if (in != null && in.getDestinationNickname() != null)
|
||||
|
||||
@@ -29,11 +29,12 @@ import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.message.GarlicMessageHandler;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.router.startup.CreateRouterInfoJob;
|
||||
import net.i2p.router.startup.StartupJob;
|
||||
import net.i2p.router.startup.WorkingDir;
|
||||
import net.i2p.router.tasks.*;
|
||||
@@ -98,10 +99,6 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
/** this does not put an 'H' in your routerInfo **/
|
||||
public final static String PROP_HIDDEN_HIDDEN = "router.isHidden";
|
||||
public final static String PROP_DYNAMIC_KEYS = "router.dynamicKeys";
|
||||
public final static String PROP_INFO_FILENAME = "router.info.location";
|
||||
public final static String PROP_INFO_FILENAME_DEFAULT = "router.info";
|
||||
public final static String PROP_KEYS_FILENAME = "router.keys.location";
|
||||
public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys";
|
||||
public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress";
|
||||
public final static String DNS_CACHE_TIME = "" + (5*60);
|
||||
private static final String EVENTLOG = "eventlog.txt";
|
||||
@@ -672,20 +669,6 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
return Boolean.parseBoolean(h);
|
||||
return _context.commSystem().isInBadCountry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only called at startup via LoadRouterInfoJob and RebuildRouterInfoJob.
|
||||
* Not called by periodic RepublishLocalRouterInfoJob.
|
||||
* We don't want to change the cert on the fly as it changes the router hash.
|
||||
* RouterInfo.isHidden() checks the capability, but RouterIdentity.isHidden() checks the cert.
|
||||
* There's no reason to ever add a hidden cert?
|
||||
* @return the certificate for a new RouterInfo - probably a null cert.
|
||||
*/
|
||||
public Certificate createCertificate() {
|
||||
if (_context.getBooleanProperty(PROP_HIDDEN))
|
||||
return new Certificate(Certificate.CERTIFICATE_TYPE_HIDDEN, null);
|
||||
return Certificate.NULL_CERT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.3
|
||||
@@ -698,16 +681,18 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
* Ugly list of files that we need to kill if we are building a new identity
|
||||
*
|
||||
*/
|
||||
private static final String _rebuildFiles[] = new String[] { "router.info",
|
||||
"router.keys",
|
||||
"netDb/my.info", // no longer used
|
||||
"connectionTag.keys", // never used?
|
||||
"keyBackup/privateEncryption.key",
|
||||
"keyBackup/privateSigning.key",
|
||||
"keyBackup/publicEncryption.key",
|
||||
"keyBackup/publicSigning.key",
|
||||
"sessionKeys.dat" // no longer used
|
||||
};
|
||||
private static final String _rebuildFiles[] = new String[] {
|
||||
CreateRouterInfoJob.INFO_FILENAME,
|
||||
CreateRouterInfoJob.KEYS_FILENAME,
|
||||
CreateRouterInfoJob.KEYS2_FILENAME,
|
||||
"netDb/my.info", // no longer used
|
||||
"connectionTag.keys", // never used?
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_ENC,
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_ENC,
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_SIGNING,
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_SIGNING,
|
||||
"sessionKeys.dat" // no longer used
|
||||
};
|
||||
|
||||
public void killKeys() {
|
||||
//new Exception("Clearing identity files").printStackTrace();
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.peermanager.TunnelHistory;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateAverages;
|
||||
|
||||
@@ -12,8 +12,10 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.i2cp.BandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.CreateLeaseSetMessage;
|
||||
import net.i2p.data.i2cp.CreateSessionMessage;
|
||||
@@ -37,6 +39,7 @@ import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
import net.i2p.data.i2cp.SetDateMessage;
|
||||
import net.i2p.router.ClientTunnelSettings;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PasswordManager;
|
||||
@@ -81,8 +84,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_log.debug("Message received: \n" + message);
|
||||
int type = message.getType();
|
||||
if (!_authorized) {
|
||||
// TODO change to default true
|
||||
boolean strict = _context.getBooleanProperty(PROP_AUTH_STRICT);
|
||||
// Default true as of 0.9.16
|
||||
boolean strict = _context.getBooleanPropertyDefaultTrue(PROP_AUTH_STRICT);
|
||||
if ((strict && type != GetDateMessage.MESSAGE_TYPE) ||
|
||||
(type != CreateSessionMessage.MESSAGE_TYPE &&
|
||||
type != GetDateMessage.MESSAGE_TYPE &&
|
||||
@@ -367,8 +370,41 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_runner.disconnectClient("Invalid CreateLeaseSetMessage");
|
||||
return;
|
||||
}
|
||||
|
||||
_context.keyManager().registerKeys(message.getLeaseSet().getDestination(), message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
Destination dest = _runner.getConfig().getDestination();
|
||||
Destination ndest = message.getLeaseSet().getDestination();
|
||||
if (!dest.equals(ndest)) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Different destination in LS");
|
||||
_runner.disconnectClient("Different destination in LS");
|
||||
return;
|
||||
}
|
||||
LeaseSetKeys keys = _context.keyManager().getKeys(dest);
|
||||
if (keys == null ||
|
||||
!message.getPrivateKey().equals(keys.getDecryptionKey())) {
|
||||
// Verify and register crypto keys if new or if changed
|
||||
// Private crypto key should never change, and if it does,
|
||||
// one of the checks below will fail
|
||||
PublicKey pk;
|
||||
try {
|
||||
pk = message.getPrivateKey().toPublic();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Bad private key in LS");
|
||||
_runner.disconnectClient("Bad private key in LS");
|
||||
return;
|
||||
}
|
||||
if (!pk.equals(message.getLeaseSet().getEncryptionKey())) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Private/public crypto key mismatch in LS");
|
||||
_runner.disconnectClient("Private/public crypto key mismatch in LS");
|
||||
return;
|
||||
}
|
||||
// just register new SPK, don't verify, unused
|
||||
_context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
} else if (!message.getSigningPrivateKey().equals(keys.getRevocationKey())) {
|
||||
// just register new SPK, don't verify, unused
|
||||
_context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
}
|
||||
try {
|
||||
_context.netDb().publish(message.getLeaseSet());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
|
||||
@@ -38,7 +38,11 @@ class LookupDestJob extends JobImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* One of h or name non-null
|
||||
* One of h or name non-null.
|
||||
*
|
||||
* For hash or b32 name, the dest will be returned if the LS can be found,
|
||||
* even if the dest uses unsupported crypto.
|
||||
*
|
||||
* @param reqID must be >= 0 if name != null
|
||||
* @param sessID must non-null if reqID >= 0
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
@@ -88,7 +92,7 @@ class LookupDestJob extends JobImpl {
|
||||
returnFail();
|
||||
} else {
|
||||
DoneJob done = new DoneJob(getContext());
|
||||
getContext().netDb().lookupLeaseSet(_hash, done, done, _timeout, _fromLocalDest);
|
||||
getContext().netDb().lookupDestination(_hash, done, _timeout, _fromLocalDest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +102,9 @@ class LookupDestJob extends JobImpl {
|
||||
}
|
||||
public String getName() { return "LeaseSet Lookup Reply to Client"; }
|
||||
public void runJob() {
|
||||
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_hash);
|
||||
if (ls != null)
|
||||
returnDest(ls.getDestination());
|
||||
Destination dest = getContext().netDb().lookupDestinationLocally(_hash);
|
||||
if (dest != null)
|
||||
returnDest(dest);
|
||||
else
|
||||
returnFail();
|
||||
}
|
||||
|
||||
@@ -15,16 +15,17 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
private Map<Hash, RouterInfo> _routers;
|
||||
private RouterContext _context;
|
||||
private final Map<Hash, RouterInfo> _routers;
|
||||
private final RouterContext _context;
|
||||
|
||||
public DummyNetworkDatabaseFacade(RouterContext ctx) {
|
||||
_routers = Collections.synchronizedMap(new HashMap<Hash, RouterInfo>());
|
||||
@@ -42,6 +43,11 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {}
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest) {}
|
||||
public LeaseSet lookupLeaseSetLocally(Hash key) { return null; }
|
||||
|
||||
public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {}
|
||||
|
||||
public Destination lookupDestinationLocally(Hash key) { return null; }
|
||||
|
||||
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
RouterInfo info = lookupRouterInfoLocally(key);
|
||||
if (info == null)
|
||||
@@ -50,13 +56,16 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.jobQueue().addJob(onFindJob);
|
||||
}
|
||||
public RouterInfo lookupRouterInfoLocally(Hash key) { return _routers.get(key); }
|
||||
|
||||
public void publish(LeaseSet localLeaseSet) {}
|
||||
public void publish(RouterInfo localRouterInfo) {}
|
||||
|
||||
public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; }
|
||||
public RouterInfo store(Hash key, RouterInfo routerInfo) {
|
||||
RouterInfo rv = _routers.put(key, routerInfo);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void unpublish(LeaseSet localLeaseSet) {}
|
||||
public void fail(Hash dbEntry) {
|
||||
_routers.remove(dbEntry);
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.List;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.message;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.HandlerJobBuilder;
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.message;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
@@ -18,7 +18,7 @@ import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
@@ -425,12 +425,19 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
getContext().statManager().addRateData("client.leaseSetFailedRemoteTime", lookupTime);
|
||||
}
|
||||
|
||||
//if (_finished == Result.NONE) {
|
||||
|
||||
int cause;
|
||||
if (getContext().netDb().isNegativeCachedForever(_to.calculateHash())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to send to " + _toString + " because the sig type is unsupported");
|
||||
cause = MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to send to " + _toString + " because we couldn't find their leaseSet");
|
||||
//}
|
||||
cause = MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET;
|
||||
}
|
||||
|
||||
dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET);
|
||||
dieFatal(cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ package net.i2p.router.message;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
|
||||
@@ -14,8 +14,8 @@ import java.util.Set;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
@@ -25,8 +25,8 @@ import gnu.getopt.Getopt;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.transport.BadCountries;
|
||||
import net.i2p.router.transport.GeoIP;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -15,6 +15,8 @@ import java.util.Set;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
@@ -71,15 +73,15 @@ class ExploreJob extends SearchJob {
|
||||
* and PeerSelector doesn't include the floodfill peers,
|
||||
* so we add the ff peers ourselves and then use the regular PeerSelector.
|
||||
*
|
||||
* TODO should we encrypt this also like we do for normal lookups?
|
||||
* Could the OBEP capture it and reply with a reference to a hostile peer?
|
||||
*
|
||||
* @param replyTunnelId tunnel to receive replies through
|
||||
* @param replyGateway gateway for the reply tunnel
|
||||
* @param expiration when the search should stop
|
||||
* @param peer the peer to send it to
|
||||
*
|
||||
* @return a DatabaseLookupMessage or GarlicMessage
|
||||
*/
|
||||
@Override
|
||||
protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration) {
|
||||
protected I2NPMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration, RouterInfo peer) {
|
||||
DatabaseLookupMessage msg = new DatabaseLookupMessage(getContext(), true);
|
||||
msg.setSearchKey(getState().getTarget());
|
||||
msg.setFrom(replyGateway);
|
||||
@@ -127,7 +129,27 @@ class ExploreJob extends SearchJob {
|
||||
_log.debug("Peers we don't want to hear about: " + dontIncludePeers);
|
||||
|
||||
msg.setDontIncludePeers(dontIncludePeers);
|
||||
return msg;
|
||||
|
||||
// Now encrypt if we can
|
||||
I2NPMessage outMsg;
|
||||
if (getContext().getProperty(IterativeSearchJob.PROP_ENCRYPT_RI, IterativeSearchJob.DEFAULT_ENCRYPT_RI)) {
|
||||
// request encrypted reply?
|
||||
if (DatabaseLookupMessage.supportsEncryptedReplies(peer)) {
|
||||
MessageWrapper.OneTimeSession sess;
|
||||
sess = MessageWrapper.generateSession(getContext());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Requesting encrypted reply from " + peer.getIdentity().calculateHash() +
|
||||
' ' + sess.key + ' ' + sess.tag);
|
||||
msg.setReplySession(sess.key, sess.tag);
|
||||
}
|
||||
outMsg = MessageWrapper.wrap(getContext(), msg, peer);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Encrypted exploratory DLM for " + getState().getTarget() + " to " +
|
||||
peer.getIdentity().calculateHash());
|
||||
} else {
|
||||
outMsg = msg;
|
||||
}
|
||||
return outMsg;
|
||||
}
|
||||
|
||||
/** max # of concurrent searches */
|
||||
|
||||
@@ -2,10 +2,10 @@ package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.RouterContext;
|
||||
@@ -62,6 +62,9 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
|
||||
} else {
|
||||
getContext().netDb().store(dsm.getKey(), (RouterInfo) dsm.getEntry());
|
||||
}
|
||||
} catch (UnsupportedCryptoException uce) {
|
||||
_search.failed();
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(_search.getJobId() + ": Received an invalid store reply", iae);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ package net.i2p.router.networkdb.kademlia;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.HandlerJobBuilder;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ package net.i2p.router.networkdb.kademlia;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.HandlerJobBuilder;
|
||||
|
||||
@@ -3,8 +3,8 @@ package net.i2p.router.networkdb.kademlia;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
+10
-25
@@ -7,11 +7,12 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
@@ -31,7 +32,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
private final Set<Hash> _verifiesInProgress;
|
||||
private FloodThrottler _floodThrottler;
|
||||
private LookupThrottler _lookupThrottler;
|
||||
private NegativeLookupCache _negativeCache;
|
||||
|
||||
/**
|
||||
* This is the flood redundancy. Entries are
|
||||
@@ -65,7 +65,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
_context.statManager().createRateStat("netDb.searchReplyNotValidated", "How many search replies we get that we are NOT able to validate (fetch)", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.searchReplyValidationSkipped", "How many search replies we get from unreliable peers that we skip?", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.republishQuantity", "How many peers do we need to send a found leaseSet to?", "NetworkDatabase", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,7 +72,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
super.startup();
|
||||
_context.jobQueue().addJob(new FloodfillMonitorJob(_context, this));
|
||||
_lookupThrottler = new LookupThrottler();
|
||||
_negativeCache = new NegativeLookupCache();
|
||||
|
||||
// refresh old routers
|
||||
Job rrj = new RefreshRoutersJob(_context, this);
|
||||
@@ -171,25 +169,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
return _lookupThrottler.shouldThrottle(from, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment in the negative lookup cache
|
||||
* @since 0.9.4
|
||||
*/
|
||||
void lookupFailed(Hash key) {
|
||||
_negativeCache.lookupFailed(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the key in the negative lookup cache?
|
||||
* @since 0.9.4
|
||||
*/
|
||||
boolean isNegativeCached(Hash key) {
|
||||
boolean rv = _negativeCache.isCached(key);
|
||||
if (rv)
|
||||
_context.statManager().addRateData("netDb.negativeCache", 1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to a subset of all floodfill peers.
|
||||
* We do this to implement Kademlia within the floodfills, i.e.
|
||||
@@ -301,7 +280,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using exploratory tunnels
|
||||
* Lookup using exploratory tunnels.
|
||||
*
|
||||
* Caller should check negative cache and/or banlist before calling.
|
||||
*
|
||||
* Begin a kademlia style search for the key specified, which can take up to timeoutMs and
|
||||
* will fire the appropriate jobs on success or timeout (or if the kademlia search completes
|
||||
@@ -315,7 +296,10 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Lookup using the client's tunnels.
|
||||
*
|
||||
* Caller should check negative cache and/or banlist before calling.
|
||||
*
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @return null always
|
||||
* @since 0.9.10
|
||||
@@ -473,6 +457,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
// should we skip the search?
|
||||
if (_floodfillEnabled ||
|
||||
_context.jobQueue().getMaxLag() > 500 ||
|
||||
_context.banlist().isBanlistedForever(peer) ||
|
||||
getKBucketSetSize() > MAX_DB_BEFORE_SKIPPING_SEARCH) {
|
||||
// don't try to overload ourselves (e.g. failing 3000 router refs at
|
||||
// once, and then firing off 3000 netDb lookup tasks)
|
||||
|
||||
@@ -18,8 +18,8 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.SelectionCollector;
|
||||
import net.i2p.kademlia.XORComparator;
|
||||
|
||||
@@ -6,9 +6,10 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
@@ -173,9 +174,9 @@ class FloodfillVerifyStoreJob extends JobImpl {
|
||||
FloodfillPeerSelector sel = (FloodfillPeerSelector)_facade.getPeerSelector();
|
||||
Certificate keyCert = null;
|
||||
if (!_isRouterInfo) {
|
||||
LeaseSet ls = _facade.lookupLeaseSetLocally(_key);
|
||||
if (ls != null) {
|
||||
Certificate cert = ls.getDestination().getCertificate();
|
||||
Destination dest = _facade.lookupDestinationLocally(_key);
|
||||
if (dest != null) {
|
||||
Certificate cert = dest.getCertificate();
|
||||
if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY)
|
||||
keyCert = cert;
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ package net.i2p.router.networkdb.kademlia;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
|
||||
+19
-7
@@ -14,9 +14,9 @@ import java.util.Date;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
@@ -51,6 +51,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
long recvBegin = System.currentTimeMillis();
|
||||
|
||||
String invalidMessage = null;
|
||||
// set if invalid store but not his fault
|
||||
boolean dontBlamePeer = false;
|
||||
boolean wasNew = false;
|
||||
RouterInfo prevNetDb = null;
|
||||
Hash key = _message.getKey();
|
||||
@@ -72,6 +74,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (getContext().clientManager().isLocal(key)) {
|
||||
//getContext().statManager().addRateData("netDb.storeLocalLeaseSetAttempt", 1, 0);
|
||||
// throw rather than return, so that we send the ack below (prevent easy attack)
|
||||
dontBlamePeer = true;
|
||||
throw new IllegalArgumentException("Peer attempted to store local leaseSet: " +
|
||||
key.toBase64().substring(0, 4));
|
||||
}
|
||||
@@ -114,6 +117,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
//if (!ls.getReceivedAsReply())
|
||||
// match.setReceivedAsPublished(true);
|
||||
}
|
||||
} catch (UnsupportedCryptoException uce) {
|
||||
invalidMessage = uce.getMessage();
|
||||
dontBlamePeer = true;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
invalidMessage = iae.getMessage();
|
||||
}
|
||||
@@ -131,8 +137,10 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (getContext().routerHash().equals(key)) {
|
||||
//getContext().statManager().addRateData("netDb.storeLocalRouterInfoAttempt", 1, 0);
|
||||
// throw rather than return, so that we send the ack below (prevent easy attack)
|
||||
dontBlamePeer = true;
|
||||
throw new IllegalArgumentException("Peer attempted to store our RouterInfo");
|
||||
}
|
||||
getContext().profileManager().heardAbout(key);
|
||||
prevNetDb = getContext().netDb().store(key, ri);
|
||||
wasNew = ((null == prevNetDb) || (prevNetDb.getPublished() < ri.getPublished()));
|
||||
// Check new routerinfo address against blocklist
|
||||
@@ -152,7 +160,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
_log.warn("New address received, Blocklisting old peer " + key + ' ' + ri);
|
||||
}
|
||||
}
|
||||
getContext().profileManager().heardAbout(key);
|
||||
} catch (UnsupportedCryptoException uce) {
|
||||
invalidMessage = uce.getMessage();
|
||||
dontBlamePeer = true;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
invalidMessage = iae.getMessage();
|
||||
}
|
||||
@@ -165,14 +175,16 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
long recvEnd = System.currentTimeMillis();
|
||||
getContext().statManager().addRateData("netDb.storeRecvTime", recvEnd-recvBegin);
|
||||
|
||||
if (_message.getReplyToken() > 0)
|
||||
// ack even if invalid or unsupported
|
||||
// TODO any cases where we shouldn't?
|
||||
if (_message.getReplyToken() > 0)
|
||||
sendAck();
|
||||
long ackEnd = System.currentTimeMillis();
|
||||
|
||||
if (_from != null)
|
||||
_fromHash = _from.getHash();
|
||||
if (_fromHash != null) {
|
||||
if (invalidMessage == null) {
|
||||
if (invalidMessage == null || dontBlamePeer) {
|
||||
getContext().profileManager().dbStoreReceived(_fromHash, wasNew);
|
||||
getContext().statManager().addRateData("netDb.storeHandled", ackEnd-recvEnd);
|
||||
} else {
|
||||
@@ -180,7 +192,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Peer " + _fromHash.toBase64() + " sent bad data: " + invalidMessage);
|
||||
}
|
||||
} else if (invalidMessage != null) {
|
||||
} else if (invalidMessage != null && !dontBlamePeer) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unknown peer sent bad data: " + invalidMessage);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.router.JobImpl;
|
||||
|
||||
@@ -13,9 +13,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.XORComparator;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
@@ -28,6 +28,8 @@ import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.util.RandomIterator;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* A traditional Kademlia search that continues to search
|
||||
@@ -88,8 +90,13 @@ class IterativeSearchJob extends FloodSearchJob {
|
||||
*/
|
||||
private static final int MAX_CONCURRENT = 1;
|
||||
|
||||
/** testing */
|
||||
private static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups";
|
||||
public static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups";
|
||||
|
||||
/** only on fast boxes, for now */
|
||||
public static final boolean DEFAULT_ENCRYPT_RI =
|
||||
SystemVersion.isX86() && SystemVersion.is64Bit() &&
|
||||
!SystemVersion.isApache() && !SystemVersion.isGNU() &&
|
||||
NativeBigInteger.isNative();
|
||||
|
||||
/**
|
||||
* Lookup using exploratory tunnels
|
||||
@@ -315,7 +322,7 @@ class IterativeSearchJob extends FloodSearchJob {
|
||||
_sentTime.put(peer, Long.valueOf(now));
|
||||
|
||||
I2NPMessage outMsg = null;
|
||||
if (_isLease || getContext().getBooleanProperty(PROP_ENCRYPT_RI)) {
|
||||
if (_isLease || getContext().getProperty(PROP_ENCRYPT_RI, DEFAULT_ENCRYPT_RI)) {
|
||||
// Full ElG is fairly expensive so only do it for LS lookups
|
||||
// if we have the ff RI, garlic encrypt it
|
||||
RouterInfo ri = getContext().netDb().lookupRouterInfoLocally(peer);
|
||||
|
||||
+200
-9
@@ -19,14 +19,20 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.KeyCertificate;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.RejectTrimmer;
|
||||
import net.i2p.kademlia.SelectionCollector;
|
||||
@@ -63,6 +69,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
protected final RouterContext _context;
|
||||
private final ReseedChecker _reseedChecker;
|
||||
private volatile long _lastRIPublishTime;
|
||||
private NegativeLookupCache _negativeCache;
|
||||
|
||||
/**
|
||||
* Map of Hash to RepublishLeaseSetJob for leases we'realready managing.
|
||||
@@ -155,6 +162,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_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 });
|
||||
context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
// following are for StoreJob
|
||||
context.statManager().createRateStat("netDb.storeRouterInfoSent", "How many routerInfo store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
context.statManager().createRateStat("netDb.storeLeaseSetSent", "How many leaseSet store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
@@ -223,6 +231,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
//_ds = null;
|
||||
_exploreKeys.clear(); // hope this doesn't cause an explosion, it shouldn't.
|
||||
// _exploreKeys = null;
|
||||
_negativeCache.clear();
|
||||
}
|
||||
|
||||
public synchronized void restart() {
|
||||
@@ -262,6 +271,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
//_ds = new TransientDataStore();
|
||||
// _exploreKeys = new HashSet(64);
|
||||
_dbDir = dbDir;
|
||||
_negativeCache = new NegativeLookupCache();
|
||||
|
||||
createHandlers();
|
||||
|
||||
@@ -480,7 +490,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using exploratory tunnels
|
||||
* Lookup using exploratory tunnels.
|
||||
* Use lookupDestination() if you don't need the LS or need it validated.
|
||||
*/
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
lookupLeaseSet(key, onFindJob, onFailedLookupJob, timeoutMs, null);
|
||||
@@ -488,6 +499,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Use lookupDestination() if you don't need the LS or need it validated.
|
||||
*
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @since 0.9.10
|
||||
*/
|
||||
@@ -500,6 +513,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_log.debug("leaseSet found locally, firing " + onFindJob);
|
||||
if (onFindJob != null)
|
||||
_context.jobQueue().addJob(onFindJob);
|
||||
} else if (isNegativeCached(key)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Negative cached, not searching: " + key);
|
||||
if (onFailedLookupJob != null)
|
||||
_context.jobQueue().addJob(onFailedLookupJob);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("leaseSet not found locally, running search");
|
||||
@@ -509,6 +527,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_log.debug("after lookupLeaseSet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Use lookupDestination() if you don't need the LS or need it validated.
|
||||
*/
|
||||
public LeaseSet lookupLeaseSetLocally(Hash key) {
|
||||
if (!_initialized) return null;
|
||||
DatabaseEntry ds = _ds.get(key);
|
||||
@@ -531,6 +552,47 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Succeeds even if LS validation and store fails due to unsupported sig type, expired, etc.
|
||||
*
|
||||
* Note that there are not separate success and fail jobs. Caller must call
|
||||
* lookupDestinationLocally() in the job to determine success.
|
||||
*
|
||||
* @param onFinishedJob non-null
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {
|
||||
if (!_initialized) return;
|
||||
Destination d = lookupDestinationLocally(key);
|
||||
if (d != null) {
|
||||
_context.jobQueue().addJob(onFinishedJob);
|
||||
} else {
|
||||
search(key, onFinishedJob, onFinishedJob, timeoutMs, true, fromLocalDest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup locally in netDB and in badDest cache
|
||||
* Succeeds even if LS validation fails due to unsupported sig type, expired, etc.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public Destination lookupDestinationLocally(Hash key) {
|
||||
if (!_initialized) return null;
|
||||
DatabaseEntry ds = _ds.get(key);
|
||||
if (ds != null) {
|
||||
if (ds.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
LeaseSet ls = (LeaseSet)ds;
|
||||
return ls.getDestination();
|
||||
}
|
||||
} else {
|
||||
return _negativeCache.getBadDest(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
if (!_initialized) return;
|
||||
@@ -538,6 +600,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
if (ri != null) {
|
||||
if (onFindJob != null)
|
||||
_context.jobQueue().addJob(onFindJob);
|
||||
} else if (_context.banlist().isBanlistedForever(key)) {
|
||||
if (onFailedLookupJob != null)
|
||||
_context.jobQueue().addJob(onFailedLookupJob);
|
||||
} else {
|
||||
search(key, onFindJob, onFailedLookupJob, timeoutMs, false);
|
||||
}
|
||||
@@ -694,9 +759,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
* Unlike for RouterInfos, this is only called once, when stored.
|
||||
* After that, LeaseSet.isCurrent() is used.
|
||||
*
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return reason why the entry is not valid, or null if it is valid
|
||||
*/
|
||||
private String validate(Hash key, LeaseSet leaseSet) {
|
||||
private String validate(Hash key, LeaseSet leaseSet) throws UnsupportedCryptoException {
|
||||
if (!key.equals(leaseSet.getDestination().calculateHash())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid store attempt! key does not match leaseSet.destination! key = "
|
||||
@@ -704,9 +770,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return "Key does not match leaseSet.destination - " + key.toBase64();
|
||||
}
|
||||
if (!leaseSet.verifySignature()) {
|
||||
// throws UnsupportedCryptoException
|
||||
processStoreFailure(key, leaseSet);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid leaseSet signature! leaseSet = " + leaseSet);
|
||||
return "Invalid leaseSet signature on " + leaseSet.getDestination().calculateHash().toBase64();
|
||||
_log.warn("Invalid leaseSet signature! " + leaseSet);
|
||||
return "Invalid leaseSet signature on " + key;
|
||||
}
|
||||
long earliest = leaseSet.getEarliestLeaseDate();
|
||||
long latest = leaseSet.getLatestLeaseDate();
|
||||
@@ -722,7 +790,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
+ " first exp. " + new Date(earliest)
|
||||
+ " last exp. " + new Date(latest),
|
||||
new Exception("Rejecting store"));
|
||||
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash().toBase64()
|
||||
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash()
|
||||
+ " expired " + DataHelper.formatDuration(age) + " ago";
|
||||
}
|
||||
if (latest > now + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) {
|
||||
@@ -739,9 +807,13 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the leaseSet
|
||||
* Store the leaseSet.
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will negative cache
|
||||
* the hash until restart.
|
||||
*
|
||||
* @throws IllegalArgumentException if the leaseSet is not valid
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return previous entry or null
|
||||
*/
|
||||
public LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException {
|
||||
@@ -798,6 +870,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
*
|
||||
* Call this only on first store, to check the key and signature once
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will banlist
|
||||
* the router hash until restart and then throw UnsupportedCrytpoException.
|
||||
*
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return reason why the entry is not valid, or null if it is valid
|
||||
*/
|
||||
private String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
|
||||
@@ -807,6 +883,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return "Key does not match routerInfo.identity";
|
||||
}
|
||||
if (!routerInfo.isValid()) {
|
||||
// throws UnsupportedCryptoException
|
||||
processStoreFailure(key, routerInfo);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
|
||||
return "Invalid routerInfo signature";
|
||||
@@ -892,15 +970,29 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
|
||||
/**
|
||||
* store the routerInfo
|
||||
* Store the routerInfo.
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will banlist
|
||||
* the router hash until restart and then throw UnsupportedCrytpoException.
|
||||
*
|
||||
* @throws IllegalArgumentException if the routerInfo is not valid
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return previous entry or null
|
||||
*/
|
||||
public RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
|
||||
return store(key, routerInfo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the routerInfo.
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will banlist
|
||||
* the router hash until restart and then throw UnsupportedCrytpoException.
|
||||
*
|
||||
* @throws IllegalArgumentException if the routerInfo is not valid
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return previous entry or null
|
||||
*/
|
||||
RouterInfo store(Hash key, RouterInfo routerInfo, boolean persist) throws IllegalArgumentException {
|
||||
if (!_initialized) return null;
|
||||
|
||||
@@ -934,6 +1026,59 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_kb.add(key);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the validate fails, call this
|
||||
* to determine if it was because of unsupported crypto.
|
||||
*
|
||||
* If so, this will banlist-forever the router hash or permanently negative cache the dest hash,
|
||||
* and then throw the exception. Otherwise it does nothing.
|
||||
*
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @since 0.9.16
|
||||
*/
|
||||
private void processStoreFailure(Hash h, DatabaseEntry entry) throws UnsupportedCryptoException {
|
||||
if (entry.getHash().equals(h)) {
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
LeaseSet ls = (LeaseSet) entry;
|
||||
Destination d = ls.getDestination();
|
||||
Certificate c = d.getCertificate();
|
||||
if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
try {
|
||||
KeyCertificate kc = c.toKeyCertificate();
|
||||
SigType type = kc.getSigType();
|
||||
if (type == null || !type.isAvailable()) {
|
||||
failPermanently(d);
|
||||
String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unsupported sig type " + stype + " for destination " + h);
|
||||
throw new UnsupportedCryptoException("Sig type " + stype);
|
||||
}
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
} else if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
|
||||
RouterInfo ri = (RouterInfo) entry;
|
||||
RouterIdentity id = ri.getIdentity();
|
||||
Certificate c = id.getCertificate();
|
||||
if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
try {
|
||||
KeyCertificate kc = c.toKeyCertificate();
|
||||
SigType type = kc.getSigType();
|
||||
if (type == null || !type.isAvailable()) {
|
||||
String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode());
|
||||
_context.banlist().banlistRouterForever(h, "Unsupported signature type " + stype);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unsupported sig type " + stype + " for router " + h);
|
||||
throw new UnsupportedCryptoException("Sig type " + stype);
|
||||
}
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Verify fail, cause unknown: " + entry);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Final remove for a leaseset.
|
||||
@@ -1005,8 +1150,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
* without any match)
|
||||
*
|
||||
* Unused - called only by FNDF.searchFull() from FloodSearchJob which is overridden - don't use this.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease) {
|
||||
throw new UnsupportedOperationException();
|
||||
/****
|
||||
if (!_initialized) return null;
|
||||
boolean isNew = true;
|
||||
SearchJob searchJob = null;
|
||||
@@ -1031,6 +1180,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.statManager().addRateData("netDb.lookupDeferred", deferred, searchJob.getExpiration()-_context.clock().now());
|
||||
}
|
||||
return searchJob;
|
||||
****/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1102,6 +1252,47 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.jobQueue().addJob(new StoreJob(_context, this, key, ds, onSuccess, onFailure, sendTimeout, toIgnore));
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment in the negative lookup cache
|
||||
*
|
||||
* @param key for Destinations or RouterIdentities
|
||||
* @since 0.9.4 moved from FNDF to KNDF in 0.9.16
|
||||
*/
|
||||
void lookupFailed(Hash key) {
|
||||
_negativeCache.lookupFailed(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the key in the negative lookup cache?
|
||||
*&
|
||||
* @param key for Destinations or RouterIdentities
|
||||
* @since 0.9.4 moved from FNDF to KNDF in 0.9.16
|
||||
*/
|
||||
boolean isNegativeCached(Hash key) {
|
||||
boolean rv = _negativeCache.isCached(key);
|
||||
if (rv)
|
||||
_context.statManager().addRateData("netDb.negativeCache", 1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negative cache until restart
|
||||
* @since 0.9.16
|
||||
*/
|
||||
void failPermanently(Destination dest) {
|
||||
_negativeCache.failPermanently(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it permanently negative cached?
|
||||
*
|
||||
* @param key only for Destinations; for RouterIdentities, see Banlist
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public boolean isNegativeCachedForever(Hash key) {
|
||||
return _negativeCache.getBadDest(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug info, HTML formatted
|
||||
* @since 0.9.10
|
||||
|
||||
@@ -8,7 +8,7 @@ import net.i2p.crypto.TagSetHandle;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import java.util.Map;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
@@ -12,11 +15,15 @@ import net.i2p.util.SimpleTimer;
|
||||
*/
|
||||
class NegativeLookupCache {
|
||||
private final ObjectCounter<Hash> counter;
|
||||
private final Map<Hash, Destination> badDests;
|
||||
|
||||
private static final int MAX_FAILS = 3;
|
||||
private static final int MAX_BAD_DESTS = 128;
|
||||
private static final long CLEAN_TIME = 2*60*1000;
|
||||
|
||||
public NegativeLookupCache() {
|
||||
this.counter = new ObjectCounter<Hash>();
|
||||
this.badDests = new LHMCache<Hash, Destination>(MAX_BAD_DESTS);
|
||||
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
|
||||
}
|
||||
|
||||
@@ -25,7 +32,46 @@ class NegativeLookupCache {
|
||||
}
|
||||
|
||||
public boolean isCached(Hash h) {
|
||||
return this.counter.count(h) >= MAX_FAILS;
|
||||
if (counter.count(h) >= MAX_FAILS)
|
||||
return true;
|
||||
synchronized(badDests) {
|
||||
return badDests.get(h) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Negative cache the hash until restart,
|
||||
* but cache the destination.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public void failPermanently(Destination dest) {
|
||||
Hash h = dest.calculateHash();
|
||||
synchronized(badDests) {
|
||||
badDests.put(h, dest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unsupported but cached Destination
|
||||
*
|
||||
* @return dest or null if not cached
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public Destination getBadDest(Hash h) {
|
||||
synchronized(badDests) {
|
||||
return badDests.get(h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public void clear() {
|
||||
counter.clear();
|
||||
synchronized(badDests) {
|
||||
badDests.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.SelectionCollector;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -29,7 +29,7 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -17,11 +17,12 @@ import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
@@ -201,22 +202,29 @@ class SearchJob extends JobImpl {
|
||||
_log.debug(getJobId() + ": Already completed");
|
||||
return;
|
||||
}
|
||||
if (_state.isAborted()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Search aborted");
|
||||
_state.complete();
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Searching: " + _state);
|
||||
if (isLocal()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Key found locally");
|
||||
_state.complete(true);
|
||||
_state.complete();
|
||||
succeed();
|
||||
} else if (isExpired()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Key search expired");
|
||||
_state.complete(true);
|
||||
_state.complete();
|
||||
fail();
|
||||
} else if (_state.getAttempted().size() > MAX_PEERS_QUERIED) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Too many peers quried");
|
||||
_state.complete(true);
|
||||
_state.complete();
|
||||
fail();
|
||||
} else {
|
||||
//_log.debug("Continuing search");
|
||||
@@ -424,7 +432,7 @@ class SearchJob extends JobImpl {
|
||||
int timeout = getPerPeerTimeoutMs(to);
|
||||
long expiration = getContext().clock().now() + timeout;
|
||||
|
||||
DatabaseLookupMessage msg = buildMessage(inTunnelId, inTunnel.getPeer(0), expiration);
|
||||
I2NPMessage msg = buildMessage(inTunnelId, inTunnel.getPeer(0), expiration, router);
|
||||
|
||||
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(to);
|
||||
if (outTunnel == null) {
|
||||
@@ -437,9 +445,9 @@ class SearchJob extends JobImpl {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Sending search to " + to
|
||||
+ " for " + msg.getSearchKey().toBase64() + " w/ replies through ["
|
||||
+ msg.getFrom().toBase64() + "] via tunnel ["
|
||||
+ msg.getReplyTunnel() + "]");
|
||||
+ " for " + getState().getTarget() + " w/ replies through "
|
||||
+ inTunnel.getPeer(0) + " via tunnel "
|
||||
+ inTunnelId);
|
||||
|
||||
SearchMessageSelector sel = new SearchMessageSelector(getContext(), router, _expiration, _state);
|
||||
SearchUpdateReplyFoundJob reply = new SearchUpdateReplyFoundJob(getContext(), router, _state, _facade,
|
||||
@@ -482,8 +490,11 @@ class SearchJob extends JobImpl {
|
||||
* @param replyTunnelId tunnel to receive replies through
|
||||
* @param replyGateway gateway for the reply tunnel
|
||||
* @param expiration when the search should stop
|
||||
* @param peer unused here; see ExploreJob extension
|
||||
*
|
||||
* @return a DatabaseLookupMessage
|
||||
*/
|
||||
protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration) {
|
||||
protected I2NPMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration, RouterInfo peer) {
|
||||
DatabaseLookupMessage msg = new DatabaseLookupMessage(getContext(), true);
|
||||
msg.setSearchKey(_state.getTarget());
|
||||
//msg.setFrom(replyGateway.getIdentity().getHash());
|
||||
|
||||
@@ -3,7 +3,7 @@ package net.i2p.router.networkdb.kademlia;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
@@ -2,7 +2,7 @@ package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -19,15 +19,16 @@ import net.i2p.router.RouterContext;
|
||||
*/
|
||||
class SearchState {
|
||||
private final RouterContext _context;
|
||||
private final HashSet<Hash> _pendingPeers;
|
||||
private final Set<Hash> _pendingPeers;
|
||||
private final Map<Hash, Long> _pendingPeerTimes;
|
||||
private final HashSet<Hash> _attemptedPeers;
|
||||
private final HashSet<Hash> _failedPeers;
|
||||
private final HashSet<Hash> _successfulPeers;
|
||||
private final HashSet<Hash> _repliedPeers;
|
||||
private final Set<Hash> _attemptedPeers;
|
||||
private final Set<Hash> _failedPeers;
|
||||
private final Set<Hash> _successfulPeers;
|
||||
private final Set<Hash> _repliedPeers;
|
||||
private final Hash _searchKey;
|
||||
private volatile long _completed;
|
||||
private volatile long _started;
|
||||
private volatile boolean _aborted;
|
||||
|
||||
public SearchState(RouterContext context, Hash key) {
|
||||
_context = context;
|
||||
@@ -87,10 +88,19 @@ class SearchState {
|
||||
return new HashSet<Hash>(_failedPeers);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean completed() { return _completed != -1; }
|
||||
public void complete(boolean completed) {
|
||||
if (completed)
|
||||
_completed = _context.clock().now();
|
||||
|
||||
public void complete() {
|
||||
_completed = _context.clock().now();
|
||||
}
|
||||
|
||||
/** @since 0.9.16 */
|
||||
public boolean isAborted() { return _aborted; }
|
||||
|
||||
/** @since 0.9.16 */
|
||||
public void abort() {
|
||||
_aborted = true;
|
||||
}
|
||||
|
||||
public long getWhenStarted() { return _started; }
|
||||
@@ -177,6 +187,8 @@ class SearchState {
|
||||
buf.append(" completed? false ");
|
||||
else
|
||||
buf.append(" completed on ").append(new Date(_completed));
|
||||
if (_aborted)
|
||||
buf.append(" (Aborted)");
|
||||
buf.append("\n\tAttempted: ");
|
||||
synchronized (_attemptedPeers) {
|
||||
buf.append(_attemptedPeers.size()).append(' ');
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.Date;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
@@ -18,24 +18,26 @@ import net.i2p.util.Log;
|
||||
/**
|
||||
* Called after a match to a db search is found
|
||||
*
|
||||
* Used only by SearchJob which is only used by ExploreJob
|
||||
*/
|
||||
class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob {
|
||||
private Log _log;
|
||||
private final Log _log;
|
||||
private I2NPMessage _message;
|
||||
private Hash _peer;
|
||||
private SearchState _state;
|
||||
private KademliaNetworkDatabaseFacade _facade;
|
||||
private SearchJob _job;
|
||||
private TunnelInfo _outTunnel;
|
||||
private TunnelInfo _replyTunnel;
|
||||
private boolean _isFloodfillPeer;
|
||||
private long _sentOn;
|
||||
private final Hash _peer;
|
||||
private final SearchState _state;
|
||||
private final KademliaNetworkDatabaseFacade _facade;
|
||||
private final SearchJob _job;
|
||||
private final TunnelInfo _outTunnel;
|
||||
private final TunnelInfo _replyTunnel;
|
||||
private final boolean _isFloodfillPeer;
|
||||
private final long _sentOn;
|
||||
|
||||
public SearchUpdateReplyFoundJob(RouterContext context, RouterInfo peer,
|
||||
SearchState state, KademliaNetworkDatabaseFacade facade,
|
||||
SearchJob job) {
|
||||
this(context, peer, state, facade, job, null, null);
|
||||
}
|
||||
|
||||
public SearchUpdateReplyFoundJob(RouterContext context, RouterInfo peer,
|
||||
SearchState state, KademliaNetworkDatabaseFacade facade,
|
||||
SearchJob job, TunnelInfo outTunnel, TunnelInfo replyTunnel) {
|
||||
@@ -52,6 +54,7 @@ class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob {
|
||||
}
|
||||
|
||||
public String getName() { return "Update Reply Found for Kademlia Search"; }
|
||||
|
||||
public void runJob() {
|
||||
if (_isFloodfillPeer)
|
||||
_job.decrementOutstandingFloodfillSearches();
|
||||
@@ -59,7 +62,7 @@ class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob {
|
||||
I2NPMessage message = _message;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Reply from " + _peer.toBase64()
|
||||
+ " with message " + message.getClass().getName());
|
||||
+ " with message " + message.getClass().getSimpleName());
|
||||
|
||||
long howLong = System.currentTimeMillis() - _sentOn;
|
||||
// assume requests are 1KB (they're almost always much smaller, but tunnels have a fixed size)
|
||||
@@ -78,34 +81,21 @@ class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob {
|
||||
|
||||
if (message instanceof DatabaseStoreMessage) {
|
||||
long timeToReply = _state.dataFound(_peer);
|
||||
|
||||
DatabaseStoreMessage msg = (DatabaseStoreMessage)message;
|
||||
DatabaseEntry entry = msg.getEntry();
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
try {
|
||||
_facade.store(msg.getKey(), (LeaseSet) entry);
|
||||
getContext().profileManager().dbLookupSuccessful(_peer, timeToReply);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.warn("Peer " + _peer + " sent us an invalid leaseSet: " + iae.getMessage());
|
||||
getContext().profileManager().dbLookupReply(_peer, 0, 0, 1, 0, timeToReply);
|
||||
}
|
||||
} else if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": dbStore received on search containing router "
|
||||
+ msg.getKey() + " with publishDate of "
|
||||
+ new Date(entry.getDate()));
|
||||
try {
|
||||
_facade.store(msg.getKey(), (RouterInfo) entry);
|
||||
getContext().profileManager().dbLookupSuccessful(_peer, timeToReply);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.warn("Peer " + _peer + " sent us an invalid routerInfo: " + iae.getMessage());
|
||||
getContext().profileManager().dbLookupReply(_peer, 0, 0, 1, 0, timeToReply);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getJobId() + ": Unknown db store type?!@ " + entry.getType());
|
||||
try {
|
||||
_facade.store(msg.getKey(), entry);
|
||||
getContext().profileManager().dbLookupSuccessful(_peer, timeToReply);
|
||||
} catch (UnsupportedCryptoException iae) {
|
||||
// don't blame the peer
|
||||
getContext().profileManager().dbLookupSuccessful(_peer, timeToReply);
|
||||
_state.abort();
|
||||
// searchNext() will call fail()
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Peer " + _peer + " sent us invalid data: ", iae);
|
||||
// blame the peer
|
||||
getContext().profileManager().dbLookupReply(_peer, 0, 0, 1, 0, timeToReply);
|
||||
}
|
||||
} else if (message instanceof DatabaseSearchReplyMessage) {
|
||||
_job.replyFound((DatabaseSearchReplyMessage)message, _peer);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -18,7 +18,7 @@ import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.MessageSelector;
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.Set;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
/**
|
||||
* Signature verification failed because the
|
||||
* sig type is unknown or unavailable.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public class UnsupportedCryptoException extends IllegalArgumentException {
|
||||
|
||||
public UnsupportedCryptoException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public UnsupportedCryptoException(String msg, Throwable t) {
|
||||
super(msg, t);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.PeerSelectionCriteria;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
|
||||
@@ -19,8 +19,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.tunnel.pool.TunnelPeerSelector;
|
||||
|
||||
@@ -16,7 +16,7 @@ import net.i2p.router.tasks.ReadConfigJob;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/** This actually boots almost everything */
|
||||
public class BootCommSystemJob extends JobImpl {
|
||||
class BootCommSystemJob extends JobImpl {
|
||||
private Log _log;
|
||||
|
||||
public static final String PROP_USE_TRUSTED_LINKS = "router.trustedLinks";
|
||||
|
||||
@@ -12,7 +12,7 @@ import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/** start up the network database */
|
||||
public class BootNetworkDbJob extends JobImpl {
|
||||
class BootNetworkDbJob extends JobImpl {
|
||||
|
||||
public BootNetworkDbJob(RouterContext ctx) {
|
||||
super(ctx);
|
||||
|
||||
@@ -12,7 +12,7 @@ import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/** start up the peer manager */
|
||||
public class BootPeerManagerJob extends JobImpl {
|
||||
class BootPeerManagerJob extends JobImpl {
|
||||
|
||||
public BootPeerManagerJob(RouterContext ctx) {
|
||||
super(ctx);
|
||||
|
||||
@@ -15,7 +15,7 @@ import net.i2p.router.RouterContext;
|
||||
/**
|
||||
* For future restricted routes. Does nothing now.
|
||||
*/
|
||||
public class BuildTrustedLinksJob extends JobImpl {
|
||||
class BuildTrustedLinksJob extends JobImpl {
|
||||
private final Job _next;
|
||||
|
||||
public BuildTrustedLinksJob(RouterContext context, Job next) {
|
||||
|
||||
@@ -12,16 +12,22 @@ import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.KeyCertificate;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
@@ -40,7 +46,14 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
private final Log _log;
|
||||
private final Job _next;
|
||||
|
||||
public CreateRouterInfoJob(RouterContext ctx, Job next) {
|
||||
public static final String INFO_FILENAME = "router.info";
|
||||
public static final String KEYS_FILENAME = "router.keys";
|
||||
public static final String KEYS2_FILENAME = "router.keys.dat";
|
||||
private static final String PROP_ROUTER_SIGTYPE = "router.sigType";
|
||||
/** TODO when changing, check isAvailable() and fallback to DSA_SHA1 */
|
||||
private static final SigType DEFAULT_SIGTYPE = SigType.DSA_SHA1;
|
||||
|
||||
CreateRouterInfoJob(RouterContext ctx, Job next) {
|
||||
super(ctx);
|
||||
_next = next;
|
||||
_log = ctx.logManager().getLog(CreateRouterInfoJob.class);
|
||||
@@ -59,9 +72,13 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
|
||||
/**
|
||||
* Writes 6 files: router.info (standard RI format),
|
||||
* router,keys, and 4 individual key files under keyBackup/
|
||||
* router.keys2, and 4 individual key files under keyBackup/
|
||||
*
|
||||
* router.keys file format: Note that this is NOT the
|
||||
* router.keys2 file format: This is the
|
||||
* same "eepPriv.dat" format used by the client code,
|
||||
* as documented in PrivateKeyFile.
|
||||
*
|
||||
* Old router.keys file format: Note that this is NOT the
|
||||
* same "eepPriv.dat" format used by the client code.
|
||||
*<pre>
|
||||
* - Private key (256 bytes)
|
||||
@@ -74,9 +91,9 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
* Caller must hold Router.routerInfoFileLock.
|
||||
*/
|
||||
RouterInfo createRouterInfo() {
|
||||
SigType type = getSigTypeConfig(getContext());
|
||||
RouterInfo info = new RouterInfo();
|
||||
OutputStream fos1 = null;
|
||||
OutputStream fos2 = null;
|
||||
try {
|
||||
info.setAddresses(getContext().commSystem().createAddresses());
|
||||
Properties stats = getContext().statPublisher().publishStatistics();
|
||||
@@ -86,21 +103,26 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
// not necessary, in constructor
|
||||
//info.setPeers(new HashSet());
|
||||
info.setPublished(getCurrentPublishDate(getContext()));
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
Certificate cert = getContext().router().createCertificate();
|
||||
ident.setCertificate(cert);
|
||||
PublicKey pubkey = null;
|
||||
PrivateKey privkey = null;
|
||||
SigningPublicKey signingPubKey = null;
|
||||
SigningPrivateKey signingPrivKey = null;
|
||||
Object keypair[] = getContext().keyGenerator().generatePKIKeypair();
|
||||
pubkey = (PublicKey)keypair[0];
|
||||
privkey = (PrivateKey)keypair[1];
|
||||
Object signingKeypair[] = getContext().keyGenerator().generateSigningKeypair();
|
||||
signingPubKey = (SigningPublicKey)signingKeypair[0];
|
||||
signingPrivKey = (SigningPrivateKey)signingKeypair[1];
|
||||
PublicKey pubkey = (PublicKey)keypair[0];
|
||||
PrivateKey privkey = (PrivateKey)keypair[1];
|
||||
SimpleDataStructure signingKeypair[] = getContext().keyGenerator().generateSigningKeys(type);
|
||||
SigningPublicKey signingPubKey = (SigningPublicKey)signingKeypair[0];
|
||||
SigningPrivateKey signingPrivKey = (SigningPrivateKey)signingKeypair[1];
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
Certificate cert = createCertificate(getContext(), signingPubKey);
|
||||
ident.setCertificate(cert);
|
||||
ident.setPublicKey(pubkey);
|
||||
ident.setSigningPublicKey(signingPubKey);
|
||||
byte[] padding;
|
||||
int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length();
|
||||
if (padLen > 0) {
|
||||
padding = new byte[padLen];
|
||||
getContext().random().nextBytes(padding);
|
||||
ident.setPadding(padding);
|
||||
} else {
|
||||
padding = null;
|
||||
}
|
||||
info.setIdentity(ident);
|
||||
|
||||
info.sign(signingPrivKey);
|
||||
@@ -108,34 +130,54 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
if (!info.isValid())
|
||||
throw new DataFormatException("RouterInfo we just built is invalid: " + info);
|
||||
|
||||
String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
|
||||
File ifile = new File(getContext().getRouterDir(), infoFilename);
|
||||
// remove router.keys
|
||||
(new File(getContext().getRouterDir(), KEYS_FILENAME)).delete();
|
||||
|
||||
// write router.info
|
||||
File ifile = new File(getContext().getRouterDir(), INFO_FILENAME);
|
||||
fos1 = new BufferedOutputStream(new SecureFileOutputStream(ifile));
|
||||
info.writeBytes(fos1);
|
||||
|
||||
String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
|
||||
File kfile = new File(getContext().getRouterDir(), keyFilename);
|
||||
fos2 = new BufferedOutputStream(new SecureFileOutputStream(kfile));
|
||||
privkey.writeBytes(fos2);
|
||||
signingPrivKey.writeBytes(fos2);
|
||||
pubkey.writeBytes(fos2);
|
||||
signingPubKey.writeBytes(fos2);
|
||||
// write router.keys.dat
|
||||
File kfile = new File(getContext().getRouterDir(), KEYS2_FILENAME);
|
||||
PrivateKeyFile pkf = new PrivateKeyFile(kfile, pubkey, signingPubKey, cert,
|
||||
privkey, signingPrivKey, padding);
|
||||
pkf.write();
|
||||
|
||||
getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey);
|
||||
|
||||
_log.info("Router info created and stored at " + ifile.getAbsolutePath() + " with private keys stored at " + kfile.getAbsolutePath() + " [" + info + "]");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Router info created and stored at " + ifile.getAbsolutePath() + " with private keys stored at " + kfile.getAbsolutePath() + " [" + info + "]");
|
||||
getContext().router().eventLog().addEvent(EventLog.REKEYED, ident.calculateHash().toBase64());
|
||||
} catch (GeneralSecurityException gse) {
|
||||
_log.log(Log.CRIT, "Error building the new router information", gse);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.log(Log.CRIT, "Error building the new router information", dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.log(Log.CRIT, "Error writing out the new router information", ioe);
|
||||
} finally {
|
||||
if (fos1 != null) try { fos1.close(); } catch (IOException ioe) {}
|
||||
if (fos2 != null) try { fos2.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* The configured SigType to expect on read-in
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public static SigType getSigTypeConfig(RouterContext ctx) {
|
||||
SigType cstype = CreateRouterInfoJob.DEFAULT_SIGTYPE;
|
||||
String sstype = ctx.getProperty(PROP_ROUTER_SIGTYPE);
|
||||
if (sstype != null) {
|
||||
SigType ntype = SigType.parseSigType(sstype);
|
||||
if (ntype != null)
|
||||
cstype = ntype;
|
||||
}
|
||||
// fallback?
|
||||
if (cstype != SigType.DSA_SHA1 && !cstype.isAvailable())
|
||||
cstype = SigType.DSA_SHA1;
|
||||
return cstype;
|
||||
}
|
||||
|
||||
/**
|
||||
* We probably don't want to expose the exact time at which a router published its info.
|
||||
@@ -146,4 +188,22 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
//_log.info("Setting published date to /now/");
|
||||
return context.clock().now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only called at startup via LoadRouterInfoJob and RebuildRouterInfoJob.
|
||||
* Not called by periodic RepublishLocalRouterInfoJob.
|
||||
* We don't want to change the cert on the fly as it changes the router hash.
|
||||
* RouterInfo.isHidden() checks the capability, but RouterIdentity.isHidden() checks the cert.
|
||||
* There's no reason to ever add a hidden cert?
|
||||
*
|
||||
* @return the certificate for a new RouterInfo - probably a null cert.
|
||||
* @since 0.9.16 moved from Router
|
||||
*/
|
||||
static Certificate createCertificate(RouterContext ctx, SigningPublicKey spk) {
|
||||
if (spk.getType() != SigType.DSA_SHA1)
|
||||
return new KeyCertificate(spk);
|
||||
if (ctx.getBooleanProperty(Router.PROP_HIDDEN))
|
||||
return new Certificate(Certificate.CERTIFICATE_TYPE_HIDDEN, null);
|
||||
return Certificate.NULL_CERT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,28 @@ import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.router.RouterPrivateKeyFile;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class LoadRouterInfoJob extends JobImpl {
|
||||
/**
|
||||
* Run once or twice at startup by StartupJob,
|
||||
* and then runs BootCommSystemJob
|
||||
*/
|
||||
class LoadRouterInfoJob extends JobImpl {
|
||||
private final Log _log;
|
||||
private RouterInfo _us;
|
||||
private static final AtomicBoolean _keyLengthChecked = new AtomicBoolean();
|
||||
@@ -45,6 +55,7 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
if (_us == null) {
|
||||
RebuildRouterInfoJob r = new RebuildRouterInfoJob(getContext());
|
||||
r.rebuildRouterInfo(false);
|
||||
// run a second time
|
||||
getContext().jobQueue().addJob(this);
|
||||
return;
|
||||
} else {
|
||||
@@ -54,18 +65,21 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads router.info and router.keys2 or router.keys.
|
||||
*
|
||||
* See CreateRouterInfoJob for file formats
|
||||
*/
|
||||
private void loadRouterInfo() {
|
||||
String routerInfoFile = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
|
||||
RouterInfo info = null;
|
||||
String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
|
||||
|
||||
File rif = new File(getContext().getRouterDir(), routerInfoFile);
|
||||
File rif = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME);
|
||||
boolean infoExists = rif.exists();
|
||||
File rkf = new File(getContext().getRouterDir(), keyFilename);
|
||||
File rkf = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS_FILENAME);
|
||||
boolean keysExist = rkf.exists();
|
||||
File rkf2 = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME);
|
||||
boolean keys2Exist = rkf2.exists();
|
||||
|
||||
InputStream fis1 = null;
|
||||
InputStream fis2 = null;
|
||||
try {
|
||||
// if we have a routerinfo but no keys, things go bad in a hurry:
|
||||
// CRIT ...rkdb.PublishLocalRouterInfoJob: Internal error - signing private key not known? rescheduling publish for 30s
|
||||
@@ -73,7 +87,7 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
// CRIT ...sport.udp.EstablishmentManager: Error in the establisher java.lang.NullPointerException
|
||||
// at net.i2p.router.transport.udp.PacketBuilder.buildSessionConfirmedPacket(PacketBuilder.java:574)
|
||||
// so pretend the RI isn't there if there is no keyfile
|
||||
if (infoExists && keysExist) {
|
||||
if (infoExists && (keys2Exist || keysExist)) {
|
||||
fis1 = new BufferedInputStream(new FileInputStream(rif));
|
||||
info = new RouterInfo();
|
||||
info.readBytes(fis1);
|
||||
@@ -85,29 +99,32 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
_us = info;
|
||||
}
|
||||
|
||||
if (keysExist) {
|
||||
fis2 = new BufferedInputStream(new FileInputStream(rkf));
|
||||
PrivateKey privkey = new PrivateKey();
|
||||
privkey.readBytes(fis2);
|
||||
if (shouldRebuild(privkey)) {
|
||||
if (keys2Exist || keysExist) {
|
||||
KeyData kd = readKeyData(rkf, rkf2);
|
||||
PublicKey pubkey = kd.routerIdentity.getPublicKey();
|
||||
SigningPublicKey signingPubKey = kd.routerIdentity.getSigningPublicKey();
|
||||
PrivateKey privkey = kd.privateKey;
|
||||
SigningPrivateKey signingPrivKey = kd.signingPrivateKey;
|
||||
SigType stype = signingPubKey.getType();
|
||||
|
||||
// check if the sigtype config changed
|
||||
SigType cstype = CreateRouterInfoJob.getSigTypeConfig(getContext());
|
||||
boolean sigTypeChanged = stype != cstype;
|
||||
|
||||
if (sigTypeChanged || shouldRebuild(privkey)) {
|
||||
if (sigTypeChanged)
|
||||
_log.logAlways(Log.WARN, "Rebuilding RouterInfo with new signature type " + cstype);
|
||||
_us = null;
|
||||
// windows... close before deleting
|
||||
if (fis1 != null) {
|
||||
try { fis1.close(); } catch (IOException ioe) {}
|
||||
fis1 = null;
|
||||
}
|
||||
try { fis2.close(); } catch (IOException ioe) {}
|
||||
fis2 = null;
|
||||
rif.delete();
|
||||
rkf.delete();
|
||||
rkf2.delete();
|
||||
return;
|
||||
}
|
||||
SigningPrivateKey signingPrivKey = new SigningPrivateKey();
|
||||
signingPrivKey.readBytes(fis2);
|
||||
PublicKey pubkey = new PublicKey();
|
||||
pubkey.readBytes(fis2);
|
||||
SigningPublicKey signingPubKey = new SigningPublicKey();
|
||||
signingPubKey.readBytes(fis2);
|
||||
|
||||
getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey);
|
||||
}
|
||||
@@ -119,12 +136,9 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
try { fis1.close(); } catch (IOException ioe2) {}
|
||||
fis1 = null;
|
||||
}
|
||||
if (fis2 != null) {
|
||||
try { fis2.close(); } catch (IOException ioe2) {}
|
||||
fis2 = null;
|
||||
}
|
||||
rif.delete();
|
||||
rkf.delete();
|
||||
rkf2.delete();
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.log(Log.CRIT, "Corrupt router info or keys at " + rif.getAbsolutePath() + " / " + rkf.getAbsolutePath(), dfe);
|
||||
_us = null;
|
||||
@@ -133,15 +147,11 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
try { fis1.close(); } catch (IOException ioe) {}
|
||||
fis1 = null;
|
||||
}
|
||||
if (fis2 != null) {
|
||||
try { fis2.close(); } catch (IOException ioe) {}
|
||||
fis2 = null;
|
||||
}
|
||||
rif.delete();
|
||||
rkf.delete();
|
||||
rkf2.delete();
|
||||
} finally {
|
||||
if (fis1 != null) try { fis1.close(); } catch (IOException ioe) {}
|
||||
if (fis2 != null) try { fis2.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,4 +184,68 @@ public class LoadRouterInfoJob extends JobImpl {
|
||||
_log.logAlways(Log.WARN, "Rebuilding RouterInfo with faster key");
|
||||
return uselong != haslong;
|
||||
}
|
||||
|
||||
/** @since 0.9.16 */
|
||||
public static class KeyData {
|
||||
public final RouterIdentity routerIdentity;
|
||||
public final PrivateKey privateKey;
|
||||
public final SigningPrivateKey signingPrivateKey;
|
||||
|
||||
public KeyData(RouterIdentity ri, PrivateKey pk, SigningPrivateKey spk) {
|
||||
routerIdentity = ri;
|
||||
privateKey = pk;
|
||||
signingPrivateKey = spk;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rkf1 in router.keys format, tried second
|
||||
* @param rkf2 in eepPriv.dat format, tried first
|
||||
* @return non-null, throws IOE if neither exisits
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public static KeyData readKeyData(File rkf1, File rkf2) throws DataFormatException, IOException {
|
||||
RouterIdentity ri;
|
||||
PrivateKey privkey;
|
||||
SigningPrivateKey signingPrivKey;
|
||||
if (rkf2.exists()) {
|
||||
RouterPrivateKeyFile pkf = new RouterPrivateKeyFile(rkf2);
|
||||
ri = pkf.getRouterIdentity();
|
||||
if (!pkf.validateKeyPairs())
|
||||
throw new DataFormatException("Key pairs invalid");
|
||||
privkey = pkf.getPrivKey();
|
||||
signingPrivKey = pkf.getSigningPrivKey();
|
||||
} else {
|
||||
InputStream fis = null;
|
||||
try {
|
||||
fis = new BufferedInputStream(new FileInputStream(rkf1));
|
||||
privkey = new PrivateKey();
|
||||
privkey.readBytes(fis);
|
||||
signingPrivKey = new SigningPrivateKey();
|
||||
signingPrivKey.readBytes(fis);
|
||||
PublicKey pubkey = new PublicKey();
|
||||
pubkey.readBytes(fis);
|
||||
SigningPublicKey signingPubKey = new SigningPublicKey();
|
||||
signingPubKey.readBytes(fis);
|
||||
|
||||
// validate
|
||||
try {
|
||||
if (!pubkey.equals(KeyGenerator.getPublicKey(privkey)))
|
||||
throw new DataFormatException("Key pairs invalid");
|
||||
if (!signingPubKey.equals(KeyGenerator.getSigningPublicKey(signingPrivKey)))
|
||||
throw new DataFormatException("Key pairs invalid");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new DataFormatException("Key pairs invalid", iae);
|
||||
}
|
||||
|
||||
ri = new RouterIdentity();
|
||||
ri.setPublicKey(pubkey);
|
||||
ri.setSigningPublicKey(signingPubKey);
|
||||
ri.setCertificate(Certificate.NULL_CERT);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return new KeyData(ri, privkey, signingPrivKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,24 @@ package net.i2p.router.startup;
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.startup.LoadRouterInfoJob.KeyData;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
@@ -44,7 +46,7 @@ import net.i2p.util.SecureFileOutputStream;
|
||||
* router.info.rebuild file is deleted
|
||||
*
|
||||
*/
|
||||
public class RebuildRouterInfoJob extends JobImpl {
|
||||
class RebuildRouterInfoJob extends JobImpl {
|
||||
private final Log _log;
|
||||
|
||||
private final static long REBUILD_DELAY = 45*1000; // every 30 seconds
|
||||
@@ -57,11 +59,11 @@ public class RebuildRouterInfoJob extends JobImpl {
|
||||
public String getName() { return "Rebuild Router Info"; }
|
||||
|
||||
public void runJob() {
|
||||
throw new UnsupportedOperationException();
|
||||
/****
|
||||
_log.debug("Testing to rebuild router info");
|
||||
String infoFile = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
|
||||
File info = new File(getContext().getRouterDir(), infoFile);
|
||||
String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
|
||||
File keyFile = new File(getContext().getRouterDir(), keyFilename);
|
||||
File info = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME);
|
||||
File keyFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME);
|
||||
|
||||
if (!info.exists() || !keyFile.exists()) {
|
||||
_log.info("Router info file [" + info.getAbsolutePath() + "] or private key file [" + keyFile.getAbsolutePath() + "] deleted, rebuilding");
|
||||
@@ -71,51 +73,37 @@ public class RebuildRouterInfoJob extends JobImpl {
|
||||
}
|
||||
getTiming().setStartAfter(getContext().clock().now() + REBUILD_DELAY);
|
||||
getContext().jobQueue().addJob(this);
|
||||
****/
|
||||
}
|
||||
|
||||
void rebuildRouterInfo() {
|
||||
rebuildRouterInfo(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param alreadyRunning unused
|
||||
*/
|
||||
void rebuildRouterInfo(boolean alreadyRunning) {
|
||||
_log.debug("Rebuilding the new router info");
|
||||
RouterInfo info = null;
|
||||
String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
|
||||
File infoFile = new File(getContext().getRouterDir(), infoFilename);
|
||||
String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
|
||||
File keyFile = new File(getContext().getRouterDir(), keyFilename);
|
||||
File infoFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME);
|
||||
File keyFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS_FILENAME);
|
||||
File keyFile2 = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME);
|
||||
|
||||
if (keyFile.exists()) {
|
||||
if (keyFile2.exists() || keyFile.exists()) {
|
||||
// ok, no need to rebuild a brand new identity, just update what we can
|
||||
RouterInfo oldinfo = getContext().router().getRouterInfo();
|
||||
if (oldinfo == null) {
|
||||
info = new RouterInfo();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(keyFile);
|
||||
PrivateKey privkey = new PrivateKey();
|
||||
privkey.readBytes(fis);
|
||||
SigningPrivateKey signingPrivKey = new SigningPrivateKey();
|
||||
signingPrivKey.readBytes(fis);
|
||||
PublicKey pubkey = new PublicKey();
|
||||
pubkey.readBytes(fis);
|
||||
SigningPublicKey signingPubKey = new SigningPublicKey();
|
||||
signingPubKey.readBytes(fis);
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
Certificate cert = getContext().router().createCertificate();
|
||||
ident.setCertificate(cert);
|
||||
ident.setPublicKey(pubkey);
|
||||
ident.setSigningPublicKey(signingPubKey);
|
||||
info.setIdentity(ident);
|
||||
KeyData kd = LoadRouterInfoJob.readKeyData(keyFile, keyFile2);
|
||||
info = new RouterInfo();
|
||||
info.setIdentity(kd.routerIdentity);
|
||||
} catch (Exception e) {
|
||||
_log.log(Log.CRIT, "Error reading in the key data from " + keyFile.getAbsolutePath(), e);
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
fis = null;
|
||||
keyFile.delete();
|
||||
keyFile2.delete();
|
||||
rebuildRouterInfo(alreadyRunning);
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
// Make a new RI from the old identity, or else info.setAddresses() will throw an ISE
|
||||
@@ -160,12 +148,14 @@ public class RebuildRouterInfoJob extends JobImpl {
|
||||
_log.warn("Private key file " + keyFile.getAbsolutePath() + " deleted! Rebuilding a brand new router identity!");
|
||||
// this proc writes the keys and info to the file as well as builds the latest and greatest info
|
||||
CreateRouterInfoJob j = new CreateRouterInfoJob(getContext(), null);
|
||||
info = j.createRouterInfo();
|
||||
synchronized (getContext().router().routerInfoFileLock) {
|
||||
info = j.createRouterInfo();
|
||||
}
|
||||
}
|
||||
|
||||
//MessageHistory.initialize();
|
||||
getContext().router().setRouterInfo(info);
|
||||
_log.info("Router info rebuilt and stored at " + infoFilename + " [" + info + "]");
|
||||
_log.info("Router info rebuilt and stored at " + infoFile + " [" + info + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/** start I2CP interface */
|
||||
public class StartAcceptingClientsJob extends JobImpl {
|
||||
class StartAcceptingClientsJob extends JobImpl {
|
||||
|
||||
public StartAcceptingClientsJob(RouterContext context) {
|
||||
super(context);
|
||||
|
||||
@@ -147,7 +147,7 @@ public class WorkingDir {
|
||||
// Check for a router.keys file or logs dir, if either exists it's an old install,
|
||||
// and only migrate the data files if told to do so
|
||||
// (router.keys could be deleted later by a killkeys())
|
||||
test = new File(oldDirf, "router.keys");
|
||||
test = new File(oldDirf, CreateRouterInfoJob.KEYS_FILENAME);
|
||||
boolean oldInstall = test.exists();
|
||||
if (!oldInstall) {
|
||||
test = new File(oldDirf, "logs");
|
||||
|
||||
@@ -31,7 +31,7 @@ public class GracefulShutdown implements Runnable {
|
||||
else if (gracefulExitCode == Router.EXIT_HARD_RESTART)
|
||||
log.log(Log.CRIT, "Restarting after a brief delay");
|
||||
else
|
||||
log.log(Log.CRIT, "Graceful shutdown progress - no more tunnels, safe to die");
|
||||
log.log(Log.CRIT, "Graceful shutdown progress: No more tunnels, starting final shutdown");
|
||||
// Allow time for a UI reponse
|
||||
try {
|
||||
synchronized (Thread.currentThread()) {
|
||||
|
||||
@@ -13,10 +13,10 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.startup.CreateRouterInfoJob;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
@@ -37,8 +37,7 @@ public class PersistRouterInfoJob extends JobImpl {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Persisting updated router info");
|
||||
|
||||
String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
|
||||
File infoFile = new File(getContext().getRouterDir(), infoFilename);
|
||||
File infoFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME);
|
||||
|
||||
RouterInfo info = getContext().router().getRouterInfo();
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -14,8 +14,8 @@ import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.transport;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
public interface TransportEventListener {
|
||||
|
||||
@@ -30,9 +30,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Job;
|
||||
|
||||
@@ -22,8 +22,8 @@ import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
@@ -59,6 +59,7 @@ public class TransportManager implements TransportEventListener {
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(TransportManager.class);
|
||||
_context.statManager().createRateStat("transport.banlistOnUnreachable", "Add a peer to the banlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.banlistOnUsupportedSigType", "Add a peer to the banlist since signature type is unsupported", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.noBidsYetNotAllUnreachable", "Add a peer to the banlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.bidFailBanlisted", "Could not attempt to bid on message, as they were banlisted", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.bidFailSelf", "Could not attempt to bid on message, as it targeted ourselves", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
@@ -499,8 +500,11 @@ public class TransportManager implements TransportEventListener {
|
||||
}
|
||||
}
|
||||
if (unreachableTransports >= _transports.size()) {
|
||||
// Don't banlist if we aren't talking to anybody, as we may have a network connection issue
|
||||
if (unreachableTransports >= _transports.size() && countActivePeers() > 0) {
|
||||
if (msg.getTarget().getIdentity().getSigningPublicKey().getType() == null) {
|
||||
_context.statManager().addRateData("transport.banlistOnUnsupportedSigType", 1);
|
||||
_context.banlist().banlistRouterForever(peer, _x("Unsupported signature type"));
|
||||
} else if (unreachableTransports >= _transports.size() && countActivePeers() > 0) {
|
||||
// Don't banlist if we aren't talking to anybody, as we may have a network connection issue
|
||||
_context.statManager().addRateData("transport.banlistOnUnreachable", msg.getLifetime(), msg.getLifetime());
|
||||
_context.banlist().banlistRouter(peer, _x("Unreachable on any transport"));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.i2p.router.transport.ntcp;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
@@ -7,11 +8,12 @@ import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
@@ -70,6 +72,7 @@ class EstablishState {
|
||||
private final byte _X[];
|
||||
private final byte _hX_xor_bobIdentHash[];
|
||||
private int _aliceIdentSize;
|
||||
private RouterIdentity _aliceIdent;
|
||||
/** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */
|
||||
private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig;
|
||||
/** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */
|
||||
@@ -112,6 +115,9 @@ class EstablishState {
|
||||
private boolean _confirmWritten;
|
||||
private boolean _failedBySkew;
|
||||
|
||||
private static final int MIN_RI_SIZE = 387;
|
||||
private static final int MAX_RI_SIZE = 2048;
|
||||
|
||||
private EstablishState() {
|
||||
_context = null;
|
||||
_log = null;
|
||||
@@ -156,7 +162,8 @@ class EstablishState {
|
||||
*/
|
||||
public void receive(ByteBuffer src) {
|
||||
if (_corrupt || _verified)
|
||||
throw new IllegalStateException(prefix() + "received after completion [corrupt?" + _corrupt + " verified? " + _verified + "] on " + _con);
|
||||
throw new IllegalStateException(prefix() + "received after completion [corrupt?" +
|
||||
_corrupt + " verified? " + _verified + "] on " + _con);
|
||||
if (!src.hasRemaining())
|
||||
return; // nothing to receive
|
||||
|
||||
@@ -185,7 +192,8 @@ class EstablishState {
|
||||
*/
|
||||
private void receiveInbound(ByteBuffer src) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"Receiving inbound: prev received=" + _received + " src.remaining=" + src.remaining());
|
||||
_log.debug(prefix() + "Receiving inbound: prev received=" + _received +
|
||||
" src.remaining=" + src.remaining());
|
||||
while (_received < _X.length && src.hasRemaining()) {
|
||||
byte c = src.get();
|
||||
_X[_received++] = c;
|
||||
@@ -269,7 +277,8 @@ class EstablishState {
|
||||
}
|
||||
SimpleByteCache.release(hxy);
|
||||
_e_hXY_tsB = new byte[toEncrypt.length];
|
||||
_context.aes().encrypt(toEncrypt, 0, _e_hXY_tsB, 0, _dh.getSessionKey(), _Y, _Y.length-16, toEncrypt.length);
|
||||
_context.aes().encrypt(toEncrypt, 0, _e_hXY_tsB, 0, _dh.getSessionKey(),
|
||||
_Y, _Y.length-16, toEncrypt.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"encrypted H(X+Y)+tsB+padding: " + Base64.encode(_e_hXY_tsB));
|
||||
byte write[] = new byte[_Y.length + _e_hXY_tsB.length];
|
||||
@@ -286,7 +295,7 @@ class EstablishState {
|
||||
}
|
||||
}
|
||||
|
||||
// ok, we are onto the encrypted area
|
||||
// ok, we are onto the encrypted area, i.e. Message #3
|
||||
while (src.hasRemaining() && !_corrupt) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
|
||||
@@ -295,7 +304,8 @@ class EstablishState {
|
||||
_received++;
|
||||
}
|
||||
if (_curEncryptedOffset >= _curEncrypted.length) {
|
||||
_context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(), _prevEncrypted, 0, _curEncrypted.length);
|
||||
_context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(),
|
||||
_prevEncrypted, 0, _curEncrypted.length);
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"full block read and decrypted: " + Base64.encode(_curDecrypted));
|
||||
|
||||
@@ -305,31 +315,59 @@ class EstablishState {
|
||||
_curEncryptedOffset = 0;
|
||||
|
||||
if (_aliceIdentSize <= 0) { // we are on the first decrypted block
|
||||
_aliceIdentSize = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + Signature.SIGNATURE_BYTES;
|
||||
int sz = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
|
||||
if (sz < MIN_RI_SIZE || sz > MAX_RI_SIZE) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
|
||||
fail("size is invalid", new Exception("size is " + sz));
|
||||
return;
|
||||
}
|
||||
_aliceIdentSize = sz;
|
||||
|
||||
// We must defer the calculations for total size of the message until
|
||||
// we get the full alice ident so
|
||||
// we can determine how long the signature is.
|
||||
// See below
|
||||
|
||||
}
|
||||
try {
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
|
||||
|
||||
if (_aliceIdent == null &&
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.size() >= 2 + _aliceIdentSize) {
|
||||
// we have enough to get Alice's RI and determine the sig+padding length
|
||||
readAliceRouterIdentity();
|
||||
if (_aliceIdent == null) {
|
||||
// readAliceRouterIdentity already called fail
|
||||
return;
|
||||
}
|
||||
SigType type = _aliceIdent.getSigningPublicKey().getType();
|
||||
if (type == null) {
|
||||
fail("Unsupported sig type");
|
||||
return;
|
||||
}
|
||||
// handle variable signature size
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + type.getSigLen();
|
||||
int rem = (_sz_aliceIdent_tsA_padding_aliceSigSize % 16);
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = 16-rem;
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize += padding;
|
||||
try {
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"alice ident size decrypted as " + _aliceIdentSize + ", making the padding at " + padding + " and total size at " + _sz_aliceIdent_tsA_padding_aliceSigSize);
|
||||
} else {
|
||||
// subsequent block...
|
||||
try {
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
|
||||
_log.debug(prefix() + "alice ident size decrypted as " + _aliceIdentSize +
|
||||
", making the padding at " + padding + " and total size at " +
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize);
|
||||
}
|
||||
|
||||
if (_aliceIdent != null &&
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
|
||||
// we have the remainder of Message #3, i.e. the padding+signature
|
||||
// Time to verify.
|
||||
|
||||
if (_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
|
||||
verifyInbound();
|
||||
if (!_corrupt && _verified && src.hasRemaining())
|
||||
prepareExtra(src);
|
||||
@@ -339,13 +377,13 @@ class EstablishState {
|
||||
+ " corrupt=" + _corrupt
|
||||
+ " verified=" + _verified + " extra=" + (_extra != null ? _extra.length : 0) + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no more bytes available in the buffer, and only a partial
|
||||
// block was read, so we can't decrypt it.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"end of available data with only a partial block read (" + _curEncryptedOffset + ", " + _received + ")");
|
||||
_log.debug(prefix() + "end of available data with only a partial block read (" +
|
||||
_curEncryptedOffset + ", " + _received + ")");
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -458,7 +496,8 @@ class EstablishState {
|
||||
//}
|
||||
|
||||
byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
|
||||
int min = 2+ident.length+4+Signature.SIGNATURE_BYTES;
|
||||
// handle variable signature size
|
||||
int min = 2 + ident.length + 4 + sig.length();
|
||||
int rem = min % 16;
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
@@ -469,10 +508,11 @@ class EstablishState {
|
||||
DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
|
||||
if (padding > 0)
|
||||
_context.random().nextBytes(preEncrypt, 2 + ident.length + 4, padding);
|
||||
System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, Signature.SIGNATURE_BYTES);
|
||||
System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, sig.length());
|
||||
|
||||
_prevEncrypted = new byte[preEncrypt.length];
|
||||
_context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(), _hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-16, preEncrypt.length);
|
||||
_context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(),
|
||||
_hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-16, preEncrypt.length);
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
//_log.debug(prefix() + "unencrypted response to Bob: " + Base64.encode(preEncrypt));
|
||||
@@ -488,13 +528,23 @@ class EstablishState {
|
||||
// recv E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
|
||||
int off = 0;
|
||||
if (_e_bobSig == null) {
|
||||
_e_bobSig = new byte[48];
|
||||
// handle variable signature size
|
||||
int siglen = _con.getRemotePeer().getSigningPublicKey().getType().getSigLen();
|
||||
int rem = siglen % 16;
|
||||
int padding;
|
||||
if (rem > 0)
|
||||
padding = 16 - rem;
|
||||
else
|
||||
padding = 0;
|
||||
_e_bobSig = new byte[siglen + padding];
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " + src.hasRemaining() + ")");
|
||||
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
|
||||
src.hasRemaining() + ")");
|
||||
} else {
|
||||
off = _received - _Y.length - _e_hXY_tsB.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " + src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
|
||||
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
|
||||
src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
|
||||
}
|
||||
while (src.hasRemaining() && off < _e_bobSig.length) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"recv bobSig received=" + _received);
|
||||
@@ -505,11 +555,15 @@ class EstablishState {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix() + "received E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev): " + Base64.encode(_e_bobSig));
|
||||
byte bobSig[] = new byte[_e_bobSig.length];
|
||||
_context.aes().decrypt(_e_bobSig, 0, bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
|
||||
_context.aes().decrypt(_e_bobSig, 0, bobSig, 0, _dh.getSessionKey(),
|
||||
_e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
|
||||
// ignore the padding
|
||||
byte bobSigData[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(bobSig, 0, bobSigData, 0, Signature.SIGNATURE_BYTES);
|
||||
Signature sig = new Signature(bobSigData);
|
||||
// handle variable signature size
|
||||
SigType type = _con.getRemotePeer().getSigningPublicKey().getType();
|
||||
int siglen = type.getSigLen();
|
||||
byte bobSigData[] = new byte[siglen];
|
||||
System.arraycopy(bobSig, 0, bobSigData, 0, siglen);
|
||||
Signature sig = new Signature(type, bobSigData);
|
||||
|
||||
byte toVerify[] = new byte[_X.length+_Y.length+Hash.HASH_LENGTH+4+4];
|
||||
int voff = 0;
|
||||
@@ -568,9 +622,60 @@ class EstablishState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob. We have received enough of message #3 from Alice
|
||||
* to get Alice's RouterIdentity.
|
||||
*
|
||||
* _aliceIdentSize must be set.
|
||||
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least 2 + _aliceIdentSize bytes.
|
||||
*
|
||||
* Sets _aliceIdent so that we
|
||||
* may determine the signature and padding sizes.
|
||||
*
|
||||
* After all of message #3 is received including the signature and
|
||||
* padding, verifyIdentity() must be called.
|
||||
*
|
||||
* @since 0.9.16 pulled out of verifyInbound()
|
||||
*/
|
||||
private void readAliceRouterIdentity() {
|
||||
if (_corrupt) return;
|
||||
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"decrypted sz(etc) data: " + Base64.encode(b));
|
||||
|
||||
try {
|
||||
int sz = _aliceIdentSize;
|
||||
if (sz < MIN_RI_SIZE || sz > MAX_RI_SIZE ||
|
||||
sz > b.length-2) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
|
||||
fail("size is invalid", new Exception("size is " + sz));
|
||||
return;
|
||||
}
|
||||
RouterIdentity alice = new RouterIdentity();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(b, 2, sz);
|
||||
alice.readBytes(bais);
|
||||
_aliceIdent = alice;
|
||||
} catch (IOException ioe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
|
||||
fail("Error verifying peer", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundDFE", 1);
|
||||
fail("Error verifying peer", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We are Bob. Verify message #3 from Alice, then send message #4 to Alice.
|
||||
*
|
||||
* _aliceIdentSize and _aliceIdent must be set.
|
||||
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least
|
||||
* (2 + _aliceIdentSize + 4 + padding + sig) bytes.
|
||||
*
|
||||
* Sets _aliceIdent so that we
|
||||
*
|
||||
* readAliceRouterIdentity() must have been called previously
|
||||
*
|
||||
* Make sure the signatures are correct, and if they are, update the
|
||||
* NIOConnection with the session key / peer ident / clock skew / iv.
|
||||
* The NIOConnection itself is responsible for registering with the
|
||||
@@ -579,22 +684,9 @@ class EstablishState {
|
||||
private void verifyInbound() {
|
||||
if (_corrupt) return;
|
||||
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"decrypted sz(etc) data: " + Base64.encode(b));
|
||||
|
||||
try {
|
||||
RouterIdentity alice = new RouterIdentity();
|
||||
int sz = (int)DataHelper.fromLong(b, 0, 2); // TO-DO: Hey zzz... Throws an NPE for me... see below, for my "quick fix", need to find out the real reason
|
||||
if ( (sz <= 0) || (sz > b.length-2-4-Signature.SIGNATURE_BYTES) ) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
|
||||
fail("size is invalid", new Exception("size is " + sz));
|
||||
return;
|
||||
}
|
||||
byte aliceData[] = new byte[sz];
|
||||
System.arraycopy(b, 2, aliceData, 0, sz);
|
||||
alice.fromByteArray(aliceData);
|
||||
int sz = _aliceIdentSize;
|
||||
long tsA = DataHelper.fromLong(b, 2+sz, 4);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(768);
|
||||
baos.write(_X);
|
||||
baos.write(_Y);
|
||||
@@ -609,26 +701,32 @@ class EstablishState {
|
||||
//_log.debug(prefix()+"check pad " + Base64.encode(b, 2+sz+4, 12));
|
||||
}
|
||||
|
||||
byte s[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
// handle variable signature size
|
||||
SigType type = _aliceIdent.getSigningPublicKey().getType();
|
||||
if (type == null) {
|
||||
fail("unsupported sig type");
|
||||
return;
|
||||
}
|
||||
byte s[] = new byte[type.getSigLen()];
|
||||
System.arraycopy(b, b.length-s.length, s, 0, s.length);
|
||||
Signature sig = new Signature(s);
|
||||
_verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey());
|
||||
Signature sig = new Signature(type, s);
|
||||
_verified = _context.dsa().verifySignature(sig, toVerify, _aliceIdent.getSigningPublicKey());
|
||||
if (_verified) {
|
||||
// get inet-addr
|
||||
InetAddress addr = this._con.getChannel().socket().getInetAddress();
|
||||
byte[] ip = (addr == null) ? null : addr.getAddress();
|
||||
if (_context.banlist().isBanlistedForever(alice.calculateHash())) {
|
||||
if (_context.banlist().isBanlistedForever(_aliceIdent.calculateHash())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping inbound connection from permanently banlisted peer: " + alice.calculateHash().toBase64());
|
||||
_log.warn("Dropping inbound connection from permanently banlisted peer: " + _aliceIdent.calculateHash());
|
||||
// So next time we will not accept the con from this IP,
|
||||
// rather than doing the whole handshake
|
||||
if(ip != null)
|
||||
_context.blocklist().add(ip);
|
||||
fail("Peer is banlisted forever: " + alice.calculateHash().toBase64());
|
||||
fail("Peer is banlisted forever: " + _aliceIdent.calculateHash());
|
||||
return;
|
||||
}
|
||||
if(ip != null)
|
||||
_transport.setIP(alice.calculateHash(), ip);
|
||||
_transport.setIP(_aliceIdent.calculateHash(), ip);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "verification successful for " + _con);
|
||||
|
||||
@@ -642,10 +740,10 @@ class EstablishState {
|
||||
_log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
|
||||
} else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSkew", diff);
|
||||
_transport.markReachable(alice.calculateHash(), true);
|
||||
_transport.markReachable(_aliceIdent.calculateHash(), true);
|
||||
// Only banlist if we know what time it is
|
||||
_context.banlist().banlistRouter(DataHelper.formatDuration(diff),
|
||||
alice.calculateHash(),
|
||||
_aliceIdent.calculateHash(),
|
||||
_x("Excessive clock skew: {0}"));
|
||||
_transport.setLastBadSkew(tsA- _tsB);
|
||||
fail("Clocks too skewed (" + diff + " ms)", null, true);
|
||||
@@ -654,27 +752,22 @@ class EstablishState {
|
||||
_log.debug(prefix()+"Clock skew: " + diff + " ms");
|
||||
}
|
||||
|
||||
sendInboundConfirm(alice, tsA);
|
||||
_con.setRemotePeer(alice);
|
||||
sendInboundConfirm(_aliceIdent, tsA);
|
||||
_con.setRemotePeer(_aliceIdent);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"e_bobSig is " + _e_bobSig.length + " bytes long");
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(_e_bobSig, _e_bobSig.length-16, iv, 0, 16);
|
||||
_con.finishInboundEstablishment(_dh.getSessionKey(), (tsA-_tsB), iv, _prevEncrypted); // skew in seconds
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(prefix()+"Verified remote peer as " + alice.calculateHash().toBase64());
|
||||
_log.info(prefix()+"Verified remote peer as " + _aliceIdent.calculateHash());
|
||||
} else {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSignature", 1);
|
||||
fail("Peer verification failed - spoof of " + alice.calculateHash().toBase64() + "?");
|
||||
fail("Peer verification failed - spoof of " + _aliceIdent.calculateHash() + "?");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
|
||||
fail("Error verifying peer", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundDFE", 1);
|
||||
fail("Error verifying peer", dfe);
|
||||
} catch(NullPointerException npe) {
|
||||
fail("Error verifying peer", npe); // TO-DO: zzz This is that quick-fix. -- Sponge
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,10 +785,19 @@ class EstablishState {
|
||||
DataHelper.toLong(toSign, off, 4, tsA); off += 4;
|
||||
DataHelper.toLong(toSign, off, 4, _tsB); off += 4;
|
||||
|
||||
// handle variable signature size
|
||||
Signature sig = _context.dsa().sign(toSign, _context.keyManager().getSigningPrivateKey());
|
||||
byte preSig[] = new byte[Signature.SIGNATURE_BYTES+8];
|
||||
System.arraycopy(sig.getData(), 0, preSig, 0, Signature.SIGNATURE_BYTES);
|
||||
_context.random().nextBytes(preSig, Signature.SIGNATURE_BYTES, 8);
|
||||
int siglen = sig.length();
|
||||
int rem = siglen % 16;
|
||||
int padding;
|
||||
if (rem > 0)
|
||||
padding = 16 - rem;
|
||||
else
|
||||
padding = 0;
|
||||
byte preSig[] = new byte[siglen + padding];
|
||||
System.arraycopy(sig.getData(), 0, preSig, 0, siglen);
|
||||
if (padding > 0)
|
||||
_context.random().nextBytes(preSig, siglen, padding);
|
||||
_e_bobSig = new byte[preSig.length];
|
||||
_context.aes().encrypt(preSig, 0, _e_bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||
|
||||
@@ -17,9 +17,9 @@ import java.util.zip.Adler32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
@@ -25,9 +25,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
@@ -362,6 +362,12 @@ public class NTCPTransport extends TransportImpl {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for supported sig type
|
||||
if (toAddress.getIdentity().getSigningPublicKey().getType() == null) {
|
||||
markUnreachable(peer);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!allowConnection()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("no bid when trying to send to " + peer + ", max connection limit reached");
|
||||
|
||||
@@ -10,9 +10,9 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
|
||||
@@ -5,11 +5,12 @@ import java.io.IOException;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
@@ -47,6 +48,9 @@ class InboundEstablishState {
|
||||
private long _receivedSignedOnTime;
|
||||
private byte _receivedSignature[];
|
||||
private boolean _verificationAttempted;
|
||||
// sig not verified
|
||||
private RouterIdentity _receivedUnconfirmedIdentity;
|
||||
// identical to uncomfirmed, but sig now verified
|
||||
private RouterIdentity _receivedConfirmedIdentity;
|
||||
// general status
|
||||
private final long _establishBegin;
|
||||
@@ -295,9 +299,28 @@ class InboundEstablishState {
|
||||
|
||||
if (cur == _receivedIdentity.length-1) {
|
||||
_receivedSignedOnTime = conf.readFinalFragmentSignedOnTime();
|
||||
if (_receivedSignature == null)
|
||||
_receivedSignature = new byte[Signature.SIGNATURE_BYTES];
|
||||
conf.readFinalSignature(_receivedSignature, 0);
|
||||
// TODO verify time to prevent replay attacks
|
||||
buildIdentity();
|
||||
if (_receivedUnconfirmedIdentity != null) {
|
||||
SigType type = _receivedUnconfirmedIdentity.getSigningPublicKey().getType();
|
||||
if (type != null) {
|
||||
int sigLen = type.getSigLen();
|
||||
if (_receivedSignature == null)
|
||||
_receivedSignature = new byte[sigLen];
|
||||
conf.readFinalSignature(_receivedSignature, 0, sigLen);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unsupported sig type from: " + toString());
|
||||
// _x() in UDPTransport
|
||||
_context.banlist().banlistRouterForever(_receivedUnconfirmedIdentity.calculateHash(),
|
||||
"Unsupported signature type");
|
||||
fail();
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad ident from: " + toString());
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
if ( (_currentState == InboundState.IB_STATE_UNKNOWN) ||
|
||||
@@ -318,9 +341,10 @@ class InboundEstablishState {
|
||||
*/
|
||||
private boolean confirmedFullyReceived() {
|
||||
if (_receivedIdentity != null) {
|
||||
for (int i = 0; i < _receivedIdentity.length; i++)
|
||||
for (int i = 0; i < _receivedIdentity.length; i++) {
|
||||
if (_receivedIdentity[i] == null)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -339,7 +363,51 @@ class InboundEstablishState {
|
||||
}
|
||||
return _receivedConfirmedIdentity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct Alice's RouterIdentity.
|
||||
* Must have received all fragments.
|
||||
* Sets _receivedUnconfirmedIdentity, unless invalid.
|
||||
*
|
||||
* Caller must synch on this.
|
||||
*
|
||||
* @since 0.9.16 was in verifyIdentity()
|
||||
*/
|
||||
private void buildIdentity() {
|
||||
if (_receivedUnconfirmedIdentity != null)
|
||||
return; // dup pkt?
|
||||
int frags = _receivedIdentity.length;
|
||||
byte[] ident;
|
||||
if (frags > 1) {
|
||||
int identSize = 0;
|
||||
for (int i = 0; i < _receivedIdentity.length; i++)
|
||||
identSize += _receivedIdentity[i].length;
|
||||
ident = new byte[identSize];
|
||||
int off = 0;
|
||||
for (int i = 0; i < _receivedIdentity.length; i++) {
|
||||
int len = _receivedIdentity[i].length;
|
||||
System.arraycopy(_receivedIdentity[i], 0, ident, off, len);
|
||||
off += len;
|
||||
}
|
||||
} else {
|
||||
// no need to copy
|
||||
ident = _receivedIdentity[0];
|
||||
}
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(ident);
|
||||
RouterIdentity peer = new RouterIdentity();
|
||||
try {
|
||||
peer.readBytes(in);
|
||||
_receivedUnconfirmedIdentity = peer;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Improperly formatted yet fully received ident", dfe);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Improperly formatted yet fully received ident", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if Alice sent us a valid confirmation packet. The
|
||||
* identity signs: Alice's IP + Alice's port + Bob's IP + Bob's port
|
||||
@@ -351,21 +419,11 @@ class InboundEstablishState {
|
||||
* Caller must synch on this.
|
||||
*/
|
||||
private void verifyIdentity() {
|
||||
int identSize = 0;
|
||||
for (int i = 0; i < _receivedIdentity.length; i++)
|
||||
identSize += _receivedIdentity[i].length;
|
||||
byte ident[] = new byte[identSize];
|
||||
int off = 0;
|
||||
for (int i = 0; i < _receivedIdentity.length; i++) {
|
||||
int len = _receivedIdentity[i].length;
|
||||
System.arraycopy(_receivedIdentity[i], 0, ident, off, len);
|
||||
off += len;
|
||||
}
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(ident);
|
||||
RouterIdentity peer = new RouterIdentity();
|
||||
try {
|
||||
peer.readBytes(in);
|
||||
|
||||
if (_receivedUnconfirmedIdentity == null)
|
||||
return; // either not yet recvd or bad ident
|
||||
if (_receivedSignature == null)
|
||||
return; // either not yet recvd or bad sig
|
||||
|
||||
byte signed[] = new byte[256+256 // X + Y
|
||||
+ _aliceIP.length + 2
|
||||
+ _bobIP.length + 2
|
||||
@@ -373,7 +431,7 @@ class InboundEstablishState {
|
||||
+ 4 // signed on time
|
||||
];
|
||||
|
||||
off = 0;
|
||||
int off = 0;
|
||||
System.arraycopy(_receivedX, 0, signed, off, _receivedX.length);
|
||||
off += _receivedX.length;
|
||||
getSentY();
|
||||
@@ -391,22 +449,15 @@ class InboundEstablishState {
|
||||
off += 4;
|
||||
DataHelper.toLong(signed, off, 4, _receivedSignedOnTime);
|
||||
Signature sig = new Signature(_receivedSignature);
|
||||
boolean ok = _context.dsa().verifySignature(sig, signed, peer.getSigningPublicKey());
|
||||
boolean ok = _context.dsa().verifySignature(sig, signed, _receivedUnconfirmedIdentity.getSigningPublicKey());
|
||||
if (ok) {
|
||||
// todo partial spoof detection - get peer.calculateHash(),
|
||||
// lookup in netdb locally, if not equal, fail?
|
||||
_receivedConfirmedIdentity = peer;
|
||||
_receivedConfirmedIdentity = _receivedUnconfirmedIdentity;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Signature failed from " + peer);
|
||||
_log.warn("Signature failed from " + _receivedUnconfirmedIdentity);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Improperly formatted yet fully received ident", dfe);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Improperly formatted yet fully received ident", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private void packetReceived() {
|
||||
|
||||
@@ -11,8 +11,8 @@ import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Addresses;
|
||||
|
||||
@@ -3,10 +3,11 @@ package net.i2p.router.transport.udp;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
@@ -41,6 +42,7 @@ class OutboundEstablishState {
|
||||
private SessionKey _sessionKey;
|
||||
private SessionKey _macKey;
|
||||
private Signature _receivedSignature;
|
||||
// includes trailing padding to mod 16
|
||||
private byte[] _receivedEncryptedSignature;
|
||||
private byte[] _receivedIV;
|
||||
// SessionConfirmed messages
|
||||
@@ -104,6 +106,7 @@ class OutboundEstablishState {
|
||||
/**
|
||||
* @param claimedAddress an IP/port based RemoteHostId, or null if unknown
|
||||
* @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
|
||||
* @param remotePeer must have supported sig type
|
||||
* @param introKey Bob's introduction key, as published in the netdb
|
||||
* @param addr non-null
|
||||
*/
|
||||
@@ -247,8 +250,20 @@ class OutboundEstablishState {
|
||||
_alicePort = reader.readPort();
|
||||
_receivedRelayTag = reader.readRelayTag();
|
||||
_receivedSignedOnTime = reader.readSignedOnTime();
|
||||
_receivedEncryptedSignature = new byte[Signature.SIGNATURE_BYTES + 8];
|
||||
reader.readEncryptedSignature(_receivedEncryptedSignature, 0);
|
||||
// handle variable signature size
|
||||
SigType type = _remotePeer.getSigningPublicKey().getType();
|
||||
if (type == null) {
|
||||
// shouldn't happen, we only connect to supported peers
|
||||
fail();
|
||||
packetReceived();
|
||||
return;
|
||||
}
|
||||
int sigLen = type.getSigLen();
|
||||
int mod = sigLen % 16;
|
||||
int pad = (mod == 0) ? 0 : (16 - mod);
|
||||
int esigLen = sigLen + pad;
|
||||
_receivedEncryptedSignature = new byte[esigLen];
|
||||
reader.readEncryptedSignature(_receivedEncryptedSignature, 0, esigLen);
|
||||
_receivedIV = new byte[UDPPacket.IV_SIZE];
|
||||
reader.readIV(_receivedIV, 0);
|
||||
|
||||
@@ -353,7 +368,9 @@ class OutboundEstablishState {
|
||||
* decrypt the signature (and subsequent pad bytes) with the
|
||||
* additional layer of encryption using the negotiated key along side
|
||||
* the packet's IV
|
||||
*
|
||||
* Caller must synch on this.
|
||||
* Only call this once! Decrypts in-place.
|
||||
*/
|
||||
private void decryptSignature() {
|
||||
if (_receivedEncryptedSignature == null) throw new NullPointerException("encrypted signature is null! this=" + this.toString());
|
||||
@@ -361,11 +378,20 @@ class OutboundEstablishState {
|
||||
if (_receivedIV == null) throw new NullPointerException("IV is null!");
|
||||
_context.aes().decrypt(_receivedEncryptedSignature, 0, _receivedEncryptedSignature, 0,
|
||||
_sessionKey, _receivedIV, _receivedEncryptedSignature.length);
|
||||
byte signatureBytes[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, Signature.SIGNATURE_BYTES);
|
||||
_receivedSignature = new Signature(signatureBytes);
|
||||
// handle variable signature size
|
||||
SigType type = _remotePeer.getSigningPublicKey().getType();
|
||||
// if type == null throws NPE
|
||||
int sigLen = type.getSigLen();
|
||||
int mod = sigLen % 16;
|
||||
if (mod != 0) {
|
||||
byte signatureBytes[] = new byte[sigLen];
|
||||
System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, sigLen);
|
||||
_receivedSignature = new Signature(type, signatureBytes);
|
||||
} else {
|
||||
_receivedSignature = new Signature(type, _receivedEncryptedSignature);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Decrypted received signature: " + Base64.encode(signatureBytes));
|
||||
_log.debug("Decrypted received signature: " + Base64.encode(_receivedSignature.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.udp.PacketBuilder.Fragment;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -74,6 +79,7 @@ class OutboundMessageFragments {
|
||||
_context.statManager().createRateStat("udp.sendVolleyTime", "Long it takes to send a full volley", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendConfirmTime", "How long it takes to send a message and get the ACK", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendConfirmFragments", "How many fragments are included in a fully ACKed message", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendFragmentsPerPacket", "How many fragments are sent in a data packet", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendConfirmVolley", "How many times did fragments need to be sent before ACK", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendFailed", "How many sends a failed message was pushed", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendAggressiveFailed", "How many volleys was a packet sent before we gave up", "udp", UDPTransport.RATES);
|
||||
@@ -81,7 +87,7 @@ class OutboundMessageFragments {
|
||||
_context.statManager().createRateStat("udp.outboundActivePeers", "How many peers we are actively sending to", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendRejected", "What volley are we on when the peer was throttled", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.partialACKReceived", "How many fragments were partially ACKed", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendSparse", "How many fragments were partially ACKed and hence not resent (time == message lifetime)", "udp", UDPTransport.RATES);
|
||||
//_context.statManager().createRateStat("udp.sendSparse", "How many fragments were partially ACKed and hence not resent (time == message lifetime)", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendPiggyback", "How many acks were piggybacked on a data packet (time == message lifetime)", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.sendPiggybackPartial", "How many partial acks were piggybacked on a data packet (time == message lifetime)", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRequiredRateStat("udp.packetsRetransmitted", "Lifetime of packets during retransmission (ms)", "udp", UDPTransport.RATES);
|
||||
@@ -236,19 +242,17 @@ class OutboundMessageFragments {
|
||||
/**
|
||||
* Fetch all the packets for a message volley, blocking until there is a
|
||||
* message which can be fully transmitted (or the transport is shut down).
|
||||
* The returned array may be sparse, with null packets taking the place of
|
||||
* already ACKed fragments.
|
||||
*
|
||||
* NOT thread-safe. Called by the PacketPusher thread only.
|
||||
*
|
||||
* @return null only on shutdown
|
||||
*/
|
||||
public UDPPacket[] getNextVolley() {
|
||||
public List<UDPPacket> getNextVolley() {
|
||||
PeerState peer = null;
|
||||
OutboundMessageState state = null;
|
||||
List<OutboundMessageState> states = null;
|
||||
// Keep track of how many we've looked at, since we don't start the iterator at the beginning.
|
||||
int peersProcessed = 0;
|
||||
while (_alive && (state == null) ) {
|
||||
while (_alive && (states == null) ) {
|
||||
int nextSendDelay = Integer.MAX_VALUE;
|
||||
// no, not every time - O(n**2) - do just before waiting below
|
||||
//finishMessages();
|
||||
@@ -275,8 +279,8 @@ class OutboundMessageFragments {
|
||||
continue;
|
||||
}
|
||||
peersProcessed++;
|
||||
state = peer.allocateSend();
|
||||
if (state != null) {
|
||||
states = peer.allocateSend();
|
||||
if (states != null) {
|
||||
// we have something to send and we will be returning it
|
||||
break;
|
||||
} else if (peersProcessed >= _activePeers.size()) {
|
||||
@@ -292,13 +296,13 @@ class OutboundMessageFragments {
|
||||
}
|
||||
}
|
||||
|
||||
if (peer != null && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Done looping, next peer we are sending for: " +
|
||||
peer.getRemotePeer());
|
||||
//if (peer != null && _log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Done looping, next peer we are sending for: " +
|
||||
// peer.getRemotePeer());
|
||||
|
||||
// if we've gone all the way through the loop, wait
|
||||
// ... unless nextSendDelay says we have more ready now
|
||||
if (state == null && peersProcessed >= _activePeers.size() && nextSendDelay > 0) {
|
||||
if (states == null && peersProcessed >= _activePeers.size() && nextSendDelay > 0) {
|
||||
_isWaiting = true;
|
||||
peersProcessed = 0;
|
||||
// why? we do this in the loop one at a time
|
||||
@@ -328,9 +332,9 @@ class OutboundMessageFragments {
|
||||
} // while alive && state == null
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending " + state);
|
||||
_log.debug("Sending " + DataHelper.toString(states));
|
||||
|
||||
UDPPacket packets[] = preparePackets(state, peer);
|
||||
List<UDPPacket> packets = preparePackets(states, peer);
|
||||
|
||||
/****
|
||||
if ( (state != null) && (state.getMessage() != null) ) {
|
||||
@@ -352,58 +356,108 @@ class OutboundMessageFragments {
|
||||
/**
|
||||
* @return null if state or peer is null
|
||||
*/
|
||||
private UDPPacket[] preparePackets(OutboundMessageState state, PeerState peer) {
|
||||
if ( (state != null) && (peer != null) ) {
|
||||
int fragments = state.getFragmentCount();
|
||||
if (fragments < 0)
|
||||
return null;
|
||||
private List<UDPPacket> preparePackets(List<OutboundMessageState> states, PeerState peer) {
|
||||
if (states == null || peer == null)
|
||||
return null;
|
||||
|
||||
// ok, simplest possible thing is to always tack on the bitfields if
|
||||
List<Long> msgIds = peer.getCurrentFullACKs();
|
||||
int newFullAckCount = msgIds.size();
|
||||
msgIds.addAll(peer.getCurrentResendACKs());
|
||||
List<ACKBitfield> partialACKBitfields = new ArrayList<ACKBitfield>();
|
||||
peer.fetchPartialACKs(partialACKBitfields);
|
||||
int piggybackedPartialACK = partialACKBitfields.size();
|
||||
// getCurrentFullACKs() already makes a copy, do we need to copy again?
|
||||
// YES because buildPacket() now removes them (maybe)
|
||||
List<Long> remaining = new ArrayList<Long>(msgIds);
|
||||
int sparseCount = 0;
|
||||
UDPPacket rv[] = new UDPPacket[fragments]; //sparse
|
||||
// ok, simplest possible thing is to always tack on the bitfields if
|
||||
List<Long> msgIds = peer.getCurrentFullACKs();
|
||||
int newFullAckCount = msgIds.size();
|
||||
msgIds.addAll(peer.getCurrentResendACKs());
|
||||
List<ACKBitfield> partialACKBitfields = new ArrayList<ACKBitfield>();
|
||||
peer.fetchPartialACKs(partialACKBitfields);
|
||||
int piggybackedPartialACK = partialACKBitfields.size();
|
||||
// getCurrentFullACKs() already makes a copy, do we need to copy again?
|
||||
// YES because buildPacket() now removes them (maybe)
|
||||
List<Long> remaining = new ArrayList<Long>(msgIds);
|
||||
|
||||
// build the list of fragments to send
|
||||
List<Fragment> toSend = new ArrayList<Fragment>(8);
|
||||
for (OutboundMessageState state : states) {
|
||||
int fragments = state.getFragmentCount();
|
||||
int queued = 0;
|
||||
for (int i = 0; i < fragments; i++) {
|
||||
if (state.needsSending(i)) {
|
||||
int before = remaining.size();
|
||||
try {
|
||||
rv[i] = _builder.buildPacket(state, i, peer, remaining, newFullAckCount, partialACKBitfields);
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
_log.log(Log.CRIT, "Corrupt trying to build a packet - please tell jrandom: " +
|
||||
partialACKBitfields + " / " + remaining + " / " + msgIds);
|
||||
sparseCount++;
|
||||
continue;
|
||||
}
|
||||
int after = remaining.size();
|
||||
newFullAckCount = Math.max(0, newFullAckCount - (before - after));
|
||||
if (rv[i] == null) {
|
||||
sparseCount++;
|
||||
continue;
|
||||
}
|
||||
rv[i].setFragmentCount(fragments);
|
||||
OutNetMessage msg = state.getMessage();
|
||||
if (msg != null)
|
||||
rv[i].setMessageType(msg.getMessageTypeId());
|
||||
else
|
||||
rv[i].setMessageType(-1);
|
||||
} else {
|
||||
sparseCount++;
|
||||
toSend.add(new Fragment(state, i));
|
||||
queued++;
|
||||
}
|
||||
}
|
||||
if (sparseCount > 0)
|
||||
remaining.clear();
|
||||
// per-state stats
|
||||
if (queued > 0 && state.getPushCount() > 1) {
|
||||
peer.messageRetransmitted(queued);
|
||||
// _packetsRetransmitted += toSend; // lifetime for the transport
|
||||
_context.statManager().addRateData("udp.peerPacketsRetransmitted", peer.getPacketsRetransmitted(), peer.getPacketsTransmitted());
|
||||
_context.statManager().addRateData("udp.packetsRetransmitted", state.getLifetime(), peer.getPacketsTransmitted());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Retransmitting " + state + " to " + peer);
|
||||
_context.statManager().addRateData("udp.sendVolleyTime", state.getLifetime(), queued);
|
||||
}
|
||||
}
|
||||
|
||||
if (toSend.isEmpty())
|
||||
return null;
|
||||
|
||||
int fragmentsToSend = toSend.size();
|
||||
// sort by size, biggest first
|
||||
// don't bother unless more than one state (fragments are already sorted within a state)
|
||||
if (fragmentsToSend > 1 && states.size() > 1)
|
||||
Collections.sort(toSend, new FragmentComparator());
|
||||
|
||||
List<Fragment> sendNext = new ArrayList<Fragment>(Math.min(toSend.size(), 4));
|
||||
List<UDPPacket> rv = new ArrayList<UDPPacket>(toSend.size());
|
||||
for (int i = 0; i < toSend.size(); i++) {
|
||||
Fragment next = toSend.get(i);
|
||||
sendNext.add(next);
|
||||
OutboundMessageState state = next.state;
|
||||
OutNetMessage msg = state.getMessage();
|
||||
int msgType = (msg != null) ? msg.getMessageTypeId() : -1;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Building packet for " + next + " to " + peer);
|
||||
int curTotalDataSize = state.fragmentSize(next.num);
|
||||
// now stuff in more fragments if they fit
|
||||
if (i +1 < toSend.size()) {
|
||||
int maxAvail = PacketBuilder.getMaxAdditionalFragmentSize(peer, sendNext.size(), curTotalDataSize);
|
||||
for (int j = i + 1; j < toSend.size(); j++) {
|
||||
next = toSend.get(j);
|
||||
int nextDataSize = next.state.fragmentSize(next.num);
|
||||
//if (PacketBuilder.canFitAnotherFragment(peer, sendNext.size(), curTotalDataSize, nextDataSize)) {
|
||||
//if (_builder.canFitAnotherFragment(peer, sendNext.size(), curTotalDataSize, nextDataSize)) {
|
||||
if (nextDataSize <= maxAvail) {
|
||||
// add it
|
||||
toSend.remove(j);
|
||||
j--;
|
||||
sendNext.add(next);
|
||||
curTotalDataSize += nextDataSize;
|
||||
maxAvail = PacketBuilder.getMaxAdditionalFragmentSize(peer, sendNext.size(), curTotalDataSize);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Adding in additional " + next + " to " + peer);
|
||||
} // else too big
|
||||
}
|
||||
}
|
||||
|
||||
int before = remaining.size();
|
||||
UDPPacket pkt = _builder.buildPacket(sendNext, peer, remaining, newFullAckCount, partialACKBitfields);
|
||||
if (pkt != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Built packet with " + sendNext.size() + " fragments totalling " + curTotalDataSize +
|
||||
" data bytes to " + peer);
|
||||
_context.statManager().addRateData("udp.sendFragmentsPerPacket", sendNext.size());
|
||||
}
|
||||
sendNext.clear();
|
||||
if (pkt == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.info("Build packet FAIL for " + DataHelper.toString(sendNext) + " to " + peer);
|
||||
continue;
|
||||
}
|
||||
rv.add(pkt);
|
||||
|
||||
int after = remaining.size();
|
||||
newFullAckCount = Math.max(0, newFullAckCount - (before - after));
|
||||
|
||||
int piggybackedAck = 0;
|
||||
if (msgIds.size() != remaining.size()) {
|
||||
for (int i = 0; i < msgIds.size(); i++) {
|
||||
Long id = msgIds.get(i);
|
||||
for (int j = 0; j < msgIds.size(); j++) {
|
||||
Long id = msgIds.get(j);
|
||||
if (!remaining.contains(id)) {
|
||||
peer.removeACKMessage(id);
|
||||
piggybackedAck++;
|
||||
@@ -411,29 +465,36 @@ class OutboundMessageFragments {
|
||||
}
|
||||
}
|
||||
|
||||
if (sparseCount > 0)
|
||||
_context.statManager().addRateData("udp.sendSparse", sparseCount, state.getLifetime());
|
||||
if (piggybackedAck > 0)
|
||||
_context.statManager().addRateData("udp.sendPiggyback", piggybackedAck, state.getLifetime());
|
||||
_context.statManager().addRateData("udp.sendPiggyback", piggybackedAck);
|
||||
if (piggybackedPartialACK - partialACKBitfields.size() > 0)
|
||||
_context.statManager().addRateData("udp.sendPiggybackPartial", piggybackedPartialACK - partialACKBitfields.size(), state.getLifetime());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Building packet for " + state + " to " + peer + " with sparse count: " + sparseCount);
|
||||
peer.packetsTransmitted(fragments - sparseCount);
|
||||
if (state.getPushCount() > 1) {
|
||||
int toSend = fragments-sparseCount;
|
||||
peer.messageRetransmitted(toSend);
|
||||
// _packetsRetransmitted += toSend; // lifetime for the transport
|
||||
_context.statManager().addRateData("udp.peerPacketsRetransmitted", peer.getPacketsRetransmitted(), peer.getPacketsTransmitted());
|
||||
_context.statManager().addRateData("udp.packetsRetransmitted", state.getLifetime(), peer.getPacketsTransmitted());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Retransmitting " + state + " to " + peer);
|
||||
_context.statManager().addRateData("udp.sendVolleyTime", state.getLifetime(), toSend);
|
||||
}
|
||||
return rv;
|
||||
} else {
|
||||
// !alive
|
||||
return null;
|
||||
|
||||
// following for debugging and stats
|
||||
pkt.setFragmentCount(sendNext.size());
|
||||
pkt.setMessageType(msgType); //type of first fragment
|
||||
}
|
||||
|
||||
|
||||
|
||||
int sent = rv.size();
|
||||
peer.packetsTransmitted(sent);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sent " + fragmentsToSend + " fragments of " + states.size() +
|
||||
" messages in " + sent + " packets to " + peer);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Biggest first
|
||||
* @since 0.9.16
|
||||
*/
|
||||
private static class FragmentComparator implements Comparator<Fragment>, Serializable {
|
||||
|
||||
public int compare(Fragment l, Fragment r) {
|
||||
// reverse
|
||||
return r.state.fragmentSize(r.num) - l.state.fragmentSize(l.num);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,10 @@ class OutboundMessageState implements CDPQEntry {
|
||||
private int _fragmentSize;
|
||||
/** size of the I2NP message */
|
||||
private int _totalSize;
|
||||
/** sends[i] is how many times the fragment has been sent, or -1 if ACKed */
|
||||
/** sends[i] is how many times the fragment has been sent, or -1 if ACKed
|
||||
* TODO this may not accurately track the number of retransmissions per-fragment,
|
||||
* and we don't make any use of it anyway, so we should just make it a bitfield.
|
||||
*/
|
||||
private short _fragmentSends[];
|
||||
private final long _startedOn;
|
||||
private long _nextSendTime;
|
||||
@@ -205,7 +208,6 @@ class OutboundMessageState implements CDPQEntry {
|
||||
}
|
||||
|
||||
public boolean needsSending(int fragment) {
|
||||
|
||||
short sends[] = _fragmentSends;
|
||||
if ( (sends == null) || (fragment >= sends.length) || (fragment < 0) )
|
||||
return false;
|
||||
@@ -225,10 +227,12 @@ class OutboundMessageState implements CDPQEntry {
|
||||
public boolean acked(ACKBitfield bitfield) {
|
||||
// stupid brute force, but the cardinality should be trivial
|
||||
short sends[] = _fragmentSends;
|
||||
if (sends != null)
|
||||
for (int i = 0; i < bitfield.fragmentCount() && i < sends.length; i++)
|
||||
if (sends != null) {
|
||||
for (int i = 0; i < bitfield.fragmentCount() && i < sends.length; i++) {
|
||||
if (bitfield.received(i))
|
||||
sends[i] = (short)-1;
|
||||
}
|
||||
}
|
||||
|
||||
boolean rv = isComplete();
|
||||
/****
|
||||
@@ -263,7 +267,10 @@ class OutboundMessageState implements CDPQEntry {
|
||||
*/
|
||||
public int getPushCount() { return _pushCount; }
|
||||
|
||||
/** note that we have pushed the message fragments */
|
||||
/**
|
||||
* Note that we have pushed the message fragments.
|
||||
* Increments push count (and max sends... why?)
|
||||
*/
|
||||
public void push() {
|
||||
// these will never be different...
|
||||
_pushCount++;
|
||||
@@ -272,7 +279,7 @@ class OutboundMessageState implements CDPQEntry {
|
||||
if (_fragmentSends != null)
|
||||
for (int i = 0; i < _fragmentSends.length; i++)
|
||||
if (_fragmentSends[i] >= (short)0)
|
||||
_fragmentSends[i] = (short)(1 + _fragmentSends[i]);
|
||||
_fragmentSends[i]++;
|
||||
|
||||
}
|
||||
|
||||
@@ -342,12 +349,15 @@ class OutboundMessageState implements CDPQEntry {
|
||||
* Throws NPE before then.
|
||||
*
|
||||
* Caller should synchronize
|
||||
*
|
||||
* @return true if fragment is not acked yet
|
||||
*/
|
||||
public boolean shouldSend(int fragmentNum) { return _fragmentSends[fragmentNum] >= (short)0; }
|
||||
|
||||
/**
|
||||
* This assumes fragment(int size) has been called
|
||||
* @param fragmentNum the number of the fragment
|
||||
*
|
||||
* @return the size of the fragment specified by the number
|
||||
*/
|
||||
public int fragmentSize(int fragmentNum) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.util.Addresses;
|
||||
@@ -130,8 +130,10 @@ class PacketBuilder {
|
||||
/** if no extended options or rekey data, which we don't support = 37 */
|
||||
public static final int HEADER_SIZE = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE + 1 + 4;
|
||||
|
||||
/** 4 byte msg ID + 3 byte fragment info */
|
||||
public static final int FRAGMENT_HEADER_SIZE = 7;
|
||||
/** not including acks. 46 */
|
||||
public static final int DATA_HEADER_SIZE = HEADER_SIZE + 9;
|
||||
public static final int DATA_HEADER_SIZE = HEADER_SIZE + 2 + FRAGMENT_HEADER_SIZE;
|
||||
|
||||
/** IPv4 only */
|
||||
public static final int IP_HEADER_SIZE = 20;
|
||||
@@ -178,6 +180,49 @@ class PacketBuilder {
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Class for passing multiple fragments to buildPacket()
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public static class Fragment {
|
||||
public final OutboundMessageState state;
|
||||
public final int num;
|
||||
|
||||
public Fragment(OutboundMessageState state, int num) {
|
||||
this.state = state;
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fragment " + num + " (" + state.fragmentSize(num) + " bytes) of " + state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will a packet to 'peer' that already has 'numFragments' fragments
|
||||
* totalling 'curDataSize' bytes fit another fragment of size 'newFragSize' ??
|
||||
*
|
||||
* This doesn't leave anything for acks.
|
||||
*
|
||||
* @param numFragments >= 1
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public static int getMaxAdditionalFragmentSize(PeerState peer, int numFragments, int curDataSize) {
|
||||
int available = peer.getMTU() - curDataSize;
|
||||
if (peer.isIPv6())
|
||||
available -= MIN_IPV6_DATA_PACKET_OVERHEAD;
|
||||
else
|
||||
available -= MIN_DATA_PACKET_OVERHEAD;
|
||||
// OVERHEAD above includes 1 * FRAGMENT+HEADER_SIZE;
|
||||
// this adds for the others, plus the new one.
|
||||
available -= numFragments * FRAGMENT_HEADER_SIZE;
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("now: " + numFragments + " / " + curDataSize + " avail: " + available);
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* This builds a data packet (PAYLOAD_TYPE_DATA).
|
||||
* See the methods below for the other message types.
|
||||
@@ -231,37 +276,65 @@ class PacketBuilder {
|
||||
public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer,
|
||||
List<Long> ackIdsRemaining, int newAckCount,
|
||||
List<ACKBitfield> partialACKsRemaining) {
|
||||
List<Fragment> frags = Collections.singletonList(new Fragment(state, fragment));
|
||||
return buildPacket(frags, peer, ackIdsRemaining, newAckCount, partialACKsRemaining);
|
||||
}
|
||||
|
||||
/*
|
||||
* Multiple fragments
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public UDPPacket buildPacket(List<Fragment> fragments, PeerState peer,
|
||||
List<Long> ackIdsRemaining, int newAckCount,
|
||||
List<ACKBitfield> partialACKsRemaining) {
|
||||
StringBuilder msg = null;
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
msg = new StringBuilder(256);
|
||||
msg.append("Data pkt to ").append(peer.getRemotePeer().toBase64());
|
||||
}
|
||||
|
||||
// calculate data size
|
||||
int numFragments = fragments.size();
|
||||
int dataSize = 0;
|
||||
for (int i = 0; i < numFragments; i++) {
|
||||
Fragment frag = fragments.get(i);
|
||||
OutboundMessageState state = frag.state;
|
||||
int fragment = frag.num;
|
||||
int sz = state.fragmentSize(fragment);
|
||||
dataSize += sz;
|
||||
if (msg != null) {
|
||||
msg.append(" Fragment ").append(i);
|
||||
msg.append(": msg ").append(state.getMessageId()).append(' ').append(fragment);
|
||||
msg.append('/').append(state.getFragmentCount());
|
||||
msg.append(' ').append(sz);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataSize < 0)
|
||||
return null;
|
||||
|
||||
// calculate size available for acks
|
||||
int currentMTU = peer.getMTU();
|
||||
int availableForAcks = currentMTU - dataSize;
|
||||
int ipHeaderSize;
|
||||
if (peer.isIPv6()) {
|
||||
availableForAcks -= MIN_IPV6_DATA_PACKET_OVERHEAD;
|
||||
ipHeaderSize = IPV6_HEADER_SIZE;
|
||||
} else {
|
||||
availableForAcks -= MIN_DATA_PACKET_OVERHEAD;
|
||||
ipHeaderSize = IP_HEADER_SIZE;
|
||||
}
|
||||
if (numFragments > 1)
|
||||
availableForAcks -= (numFragments - 1) * FRAGMENT_HEADER_SIZE;
|
||||
int availableForExplicitAcks = availableForAcks;
|
||||
|
||||
// make the packet
|
||||
UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_DATA << 4));
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = HEADER_SIZE;
|
||||
|
||||
StringBuilder msg = null;
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
msg = new StringBuilder(128);
|
||||
msg.append("Data pkt to ").append(peer.getRemotePeer().toBase64());
|
||||
msg.append(" msg ").append(state.getMessageId()).append(" frag:").append(fragment);
|
||||
msg.append('/').append(state.getFragmentCount());
|
||||
}
|
||||
|
||||
int dataSize = state.fragmentSize(fragment);
|
||||
if (dataSize < 0) {
|
||||
packet.release();
|
||||
return null;
|
||||
}
|
||||
|
||||
int currentMTU = peer.getMTU();
|
||||
int availableForAcks = currentMTU - dataSize;
|
||||
int ipHeaderSize;
|
||||
if (peer.getRemoteIP().length == 4) {
|
||||
availableForAcks -= MIN_DATA_PACKET_OVERHEAD;
|
||||
ipHeaderSize = IP_HEADER_SIZE;
|
||||
} else {
|
||||
availableForAcks -= MIN_IPV6_DATA_PACKET_OVERHEAD;
|
||||
ipHeaderSize = IPV6_HEADER_SIZE;
|
||||
}
|
||||
int availableForExplicitAcks = availableForAcks;
|
||||
|
||||
// ok, now for the body...
|
||||
|
||||
// just always ask for an ACK for now...
|
||||
@@ -299,7 +372,7 @@ class PacketBuilder {
|
||||
off++;
|
||||
|
||||
if (msg != null) {
|
||||
msg.append(" data: ").append(dataSize).append(" bytes, mtu: ")
|
||||
msg.append(" Total data: ").append(dataSize).append(" bytes, mtu: ")
|
||||
.append(currentMTU).append(", ")
|
||||
.append(newAckCount).append(" new full acks requested, ")
|
||||
.append(ackIdsRemaining.size() - newAckCount).append(" resend acks requested, ")
|
||||
@@ -325,7 +398,7 @@ class PacketBuilder {
|
||||
DataHelper.toLong(data, off, 4, ackId.longValue());
|
||||
off += 4;
|
||||
if (msg != null) // logging it
|
||||
msg.append(" full ack: ").append(ackId.longValue());
|
||||
msg.append(' ').append(ackId.longValue());
|
||||
}
|
||||
//acksIncluded = true;
|
||||
}
|
||||
@@ -357,7 +430,7 @@ class PacketBuilder {
|
||||
}
|
||||
iter.remove();
|
||||
if (msg != null) // logging it
|
||||
msg.append(" partial ack: ").append(bitfield);
|
||||
msg.append(' ').append(bitfield);
|
||||
}
|
||||
//acksIncluded = true;
|
||||
// now jump back and fill in the number of bitfields *actually* included
|
||||
@@ -367,30 +440,42 @@ class PacketBuilder {
|
||||
//if ( (msg != null) && (acksIncluded) )
|
||||
// _log.debug(msg.toString());
|
||||
|
||||
DataHelper.toLong(data, off, 1, 1); // only one fragment in this message
|
||||
DataHelper.toLong(data, off, 1, numFragments);
|
||||
off++;
|
||||
|
||||
DataHelper.toLong(data, off, 4, state.getMessageId());
|
||||
off += 4;
|
||||
// now write each fragment
|
||||
int sizeWritten = 0;
|
||||
for (int i = 0; i < numFragments; i++) {
|
||||
Fragment frag = fragments.get(i);
|
||||
OutboundMessageState state = frag.state;
|
||||
int fragment = frag.num;
|
||||
|
||||
DataHelper.toLong(data, off, 4, state.getMessageId());
|
||||
off += 4;
|
||||
|
||||
data[off] |= fragment << 1;
|
||||
if (fragment == state.getFragmentCount() - 1)
|
||||
data[off] |= 1; // isLast
|
||||
off++;
|
||||
data[off] |= fragment << 1;
|
||||
if (fragment == state.getFragmentCount() - 1)
|
||||
data[off] |= 1; // isLast
|
||||
off++;
|
||||
|
||||
DataHelper.toLong(data, off, 2, dataSize);
|
||||
data[off] &= (byte)0x3F; // 2 highest bits are reserved
|
||||
off += 2;
|
||||
int fragSize = state.fragmentSize(fragment);
|
||||
DataHelper.toLong(data, off, 2, fragSize);
|
||||
data[off] &= (byte)0x3F; // 2 highest bits are reserved
|
||||
off += 2;
|
||||
|
||||
int sizeWritten = state.writeFragment(data, off, fragment);
|
||||
int sz = state.writeFragment(data, off, fragment);
|
||||
off += sz;
|
||||
sizeWritten += sz;
|
||||
}
|
||||
|
||||
if (sizeWritten != dataSize) {
|
||||
if (sizeWritten < 0) {
|
||||
// probably already freed from OutboundMessageState
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Write failed for fragment " + fragment + " of " + state.getMessageId());
|
||||
_log.warn("Write failed for " + DataHelper.toString(fragments));
|
||||
} else {
|
||||
_log.error("Size written: " + sizeWritten + " but size: " + dataSize
|
||||
+ " for fragment " + fragment + " of " + state.getMessageId());
|
||||
_log.error("Size written: " + sizeWritten + " but size: " + dataSize +
|
||||
" for " + DataHelper.toString(fragments));
|
||||
}
|
||||
packet.release();
|
||||
return null;
|
||||
@@ -398,31 +483,44 @@ class PacketBuilder {
|
||||
// _log.debug("Size written: " + sizeWritten + " for fragment " + fragment
|
||||
// + " of " + state.getMessageId());
|
||||
}
|
||||
|
||||
// put this after writeFragment() since dataSize will be zero for use-after-free
|
||||
if (dataSize == 0) {
|
||||
// OK according to the protocol but if we send it, it's a bug
|
||||
_log.error("Sending zero-size fragment " + fragment + " of " + state + " for " + peer);
|
||||
_log.error("Sending zero-size fragment??? for " + DataHelper.toString(fragments));
|
||||
}
|
||||
off += dataSize;
|
||||
|
||||
|
||||
// pad up so we're on the encryption boundary
|
||||
off = pad1(data, off);
|
||||
off = pad2(data, off, currentMTU - (ipHeaderSize + UDP_HEADER_SIZE));
|
||||
pkt.setLength(off);
|
||||
|
||||
authenticate(packet, peer.getCurrentCipherKey(), peer.getCurrentMACKey());
|
||||
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
|
||||
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
if (msg != null) {
|
||||
// verify multi-fragment packet
|
||||
//if (numFragments > 1) {
|
||||
// msg.append("\nDataReader dump\n:");
|
||||
// UDPPacketReader reader = new UDPPacketReader(_context);
|
||||
// reader.initialize(packet);
|
||||
// UDPPacketReader.DataReader dreader = reader.getDataReader();
|
||||
// try {
|
||||
// msg.append(dreader.toString());
|
||||
// } catch (Exception e) {
|
||||
// _log.info("blowup, dump follows", e);
|
||||
// msg.append('\n');
|
||||
// msg.append(net.i2p.util.HexDump.dump(data, 0, off));
|
||||
// }
|
||||
//}
|
||||
msg.append(" pkt size ").append(off + (ipHeaderSize + UDP_HEADER_SIZE));
|
||||
_log.info(msg.toString());
|
||||
}
|
||||
|
||||
authenticate(packet, peer.getCurrentCipherKey(), peer.getCurrentMACKey());
|
||||
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
|
||||
|
||||
// the packet could have been built before the current mtu got lowered, so
|
||||
// compare to LARGE_MTU
|
||||
if (off + (ipHeaderSize + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) {
|
||||
_log.error("Size is " + off + " for " + packet +
|
||||
" fragment " + fragment +
|
||||
" data size " + dataSize +
|
||||
" pkt size " + (off + (ipHeaderSize + UDP_HEADER_SIZE)) +
|
||||
" MTU " + currentMTU +
|
||||
@@ -430,7 +528,7 @@ class PacketBuilder {
|
||||
availableForExplicitAcks + " for full acks " +
|
||||
explicitToSend + " full acks included " +
|
||||
partialAcksToSend + " partial acks included " +
|
||||
" OMS " + state, new Exception());
|
||||
" Fragments: " + DataHelper.toString(fragments), new Exception());
|
||||
}
|
||||
|
||||
return packet;
|
||||
@@ -596,14 +694,22 @@ class PacketBuilder {
|
||||
off += 4;
|
||||
DataHelper.toLong(data, off, 4, state.getSentSignedOnTime());
|
||||
off += 4;
|
||||
System.arraycopy(state.getSentSignature().getData(), 0, data, off, Signature.SIGNATURE_BYTES);
|
||||
off += Signature.SIGNATURE_BYTES;
|
||||
// ok, we need another 8 bytes of random padding
|
||||
// (ok, this only gives us 63 bits, not 64)
|
||||
long l = _context.random().nextLong();
|
||||
if (l < 0) l = 0 - l;
|
||||
DataHelper.toLong(data, off, 8, l);
|
||||
off += 8;
|
||||
|
||||
// handle variable signature size
|
||||
Signature sig = state.getSentSignature();
|
||||
int siglen = sig.length();
|
||||
System.arraycopy(sig.getData(), 0, data, off, siglen);
|
||||
off += siglen;
|
||||
// ok, we need another few bytes of random padding
|
||||
int rem = siglen % 16;
|
||||
int padding;
|
||||
if (rem > 0) {
|
||||
padding = 16 - rem;
|
||||
_context.random().nextBytes(data, off, padding);
|
||||
off += padding;
|
||||
} else {
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
@@ -612,9 +718,9 @@ class PacketBuilder {
|
||||
buf.append(" Bob: ").append(Addresses.toString(state.getReceivedOurIP(), externalPort));
|
||||
buf.append(" RelayTag: ").append(state.getSentRelayTag());
|
||||
buf.append(" SignedOn: ").append(state.getSentSignedOnTime());
|
||||
buf.append(" signature: ").append(Base64.encode(state.getSentSignature().getData()));
|
||||
buf.append(" signature: ").append(Base64.encode(sig.getData()));
|
||||
buf.append("\nRawCreated: ").append(Base64.encode(data, 0, off));
|
||||
buf.append("\nsignedTime: ").append(Base64.encode(data, off-8-Signature.SIGNATURE_BYTES-4, 4));
|
||||
buf.append("\nsignedTime: ").append(Base64.encode(data, off - padding - siglen - 4, 4));
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
@@ -623,7 +729,7 @@ class PacketBuilder {
|
||||
byte[] iv = SimpleByteCache.acquire(UDPPacket.IV_SIZE);
|
||||
_context.random().nextBytes(iv);
|
||||
|
||||
int encrWrite = Signature.SIGNATURE_BYTES + 8;
|
||||
int encrWrite = siglen + padding;
|
||||
int sigBegin = off - encrWrite;
|
||||
_context.aes().encrypt(data, sigBegin, data, sigBegin, state.getCipherKey(), iv, encrWrite);
|
||||
|
||||
@@ -774,8 +880,11 @@ class PacketBuilder {
|
||||
DataHelper.toLong(data, off, 4, state.getSentSignedOnTime());
|
||||
off += 4;
|
||||
|
||||
// handle variable signature size
|
||||
// we need to pad this so we're at the encryption boundary
|
||||
int mod = (off + Signature.SIGNATURE_BYTES) & 0x0f;
|
||||
Signature sig = state.getSentSignature();
|
||||
int siglen = sig.length();
|
||||
int mod = (off + siglen) & 0x0f;
|
||||
if (mod != 0) {
|
||||
int paddingRequired = 16 - mod;
|
||||
// add an arbitrary number of 16byte pad blocks too ???
|
||||
@@ -787,8 +896,8 @@ class PacketBuilder {
|
||||
// so trailing non-mod-16 data is ignored. That truncates the sig.
|
||||
|
||||
// BUG: NPE here if null signature
|
||||
System.arraycopy(state.getSentSignature().getData(), 0, data, off, Signature.SIGNATURE_BYTES);
|
||||
off += Signature.SIGNATURE_BYTES;
|
||||
System.arraycopy(sig.getData(), 0, data, off, siglen);
|
||||
off += siglen;
|
||||
} else {
|
||||
// We never get here (see above)
|
||||
|
||||
|
||||
@@ -37,11 +37,10 @@ class PacketPusher implements Runnable {
|
||||
public void run() {
|
||||
while (_alive) {
|
||||
try {
|
||||
UDPPacket packets[] = _fragments.getNextVolley();
|
||||
List<UDPPacket> packets = _fragments.getNextVolley();
|
||||
if (packets != null) {
|
||||
for (int i = 0; i < packets.length; i++) {
|
||||
if (packets[i] != null) // null for ACKed fragments
|
||||
send(packets[i]);
|
||||
for (int i = 0; i < packets.size(); i++) {
|
||||
send(packets.get(i));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -242,6 +242,9 @@ class PeerState {
|
||||
private static final int MINIMUM_WINDOW_BYTES = DEFAULT_SEND_WINDOW_BYTES;
|
||||
private static final int MAX_SEND_WINDOW_BYTES = 1024*1024;
|
||||
|
||||
/** max number of msgs returned from allocateSend() */
|
||||
private static final int MAX_ALLOCATE_SEND = 2;
|
||||
|
||||
/**
|
||||
* Was 32 before 0.9.2, but since the streaming lib goes up to 128,
|
||||
* we would just drop our own msgs right away during slow start.
|
||||
@@ -1563,15 +1566,16 @@ class PeerState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick a message we want to send and allocate it out of our window
|
||||
* Pick one or more messages we want to send and allocate them out of our window
|
||||
* High usage -
|
||||
* OutboundMessageFragments.getNextVolley() calls this 2nd, if finishMessages() returned > 0.
|
||||
* TODO combine finishMessages(), allocateSend(), and getNextDelay() so we don't iterate 3 times.
|
||||
*
|
||||
* @return allocated message to send, or null if no messages or no resources
|
||||
* @return allocated messages to send (never empty), or null if no messages or no resources
|
||||
*/
|
||||
public OutboundMessageState allocateSend() {
|
||||
public List<OutboundMessageState> allocateSend() {
|
||||
if (_dead) return null;
|
||||
List<OutboundMessageState> rv = null;
|
||||
synchronized (_outboundMessages) {
|
||||
for (OutboundMessageState state : _outboundMessages) {
|
||||
// We have 3 return values, because if allocateSendingBytes() returns false,
|
||||
@@ -1588,44 +1592,54 @@ class PeerState {
|
||||
msg.timestamp("not reached for allocation " + msgs.size() + " other peers");
|
||||
}
|
||||
*/
|
||||
return state;
|
||||
if (rv == null)
|
||||
rv = new ArrayList<OutboundMessageState>(MAX_ALLOCATE_SEND);
|
||||
rv.add(state);
|
||||
if (rv.size() >= MAX_ALLOCATE_SEND)
|
||||
return rv;
|
||||
} else if (should == ShouldSend.NO_BW) {
|
||||
// no more bandwidth available
|
||||
// we don't bother looking for a smaller msg that would fit.
|
||||
// By not looking further, we keep strict sending order, and that allows
|
||||
// some efficiency in acked() below.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
if (rv == null && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Nothing to send (BW) to " + _remotePeer + ", with " + _outboundMessages.size() +
|
||||
" / " + _outboundQueue.size() + " remaining");
|
||||
return null;
|
||||
return rv;
|
||||
} /* else {
|
||||
OutNetMessage msg = state.getMessage();
|
||||
if (msg != null)
|
||||
msg.timestamp("passed over for allocation with " + msgs.size() + " peers");
|
||||
} */
|
||||
}
|
||||
|
||||
// Peek at head of _outboundQueue and see if we can send it.
|
||||
// If so, pull it off, put it in _outbundMessages, test
|
||||
// again for bandwidth if necessary, and return it.
|
||||
OutboundMessageState state = _outboundQueue.peek();
|
||||
if (state != null && ShouldSend.YES == locked_shouldSend(state)) {
|
||||
OutboundMessageState state;
|
||||
while ((state = _outboundQueue.peek()) != null &&
|
||||
ShouldSend.YES == locked_shouldSend(state)) {
|
||||
// we could get a different state, or null, when we poll,
|
||||
// due to AQM drops, so we test again if necessary
|
||||
OutboundMessageState dequeuedState = _outboundQueue.poll();
|
||||
if (dequeuedState != null) {
|
||||
_outboundMessages.add(dequeuedState);
|
||||
if (dequeuedState == state || ShouldSend.YES == locked_shouldSend(dequeuedState)) {
|
||||
if (dequeuedState == state || ShouldSend.YES == locked_shouldSend(state)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Allocate sending (NEW) to " + _remotePeer + ": " + dequeuedState.getMessageId());
|
||||
return dequeuedState;
|
||||
if (rv == null)
|
||||
rv = new ArrayList<OutboundMessageState>(MAX_ALLOCATE_SEND);
|
||||
rv.add(state);
|
||||
if (rv.size() >= MAX_ALLOCATE_SEND)
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
if ( rv == null && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Nothing to send to " + _remotePeer + ", with " + _outboundMessages.size() +
|
||||
" / " + _outboundQueue.size() + " remaining");
|
||||
return null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,8 +9,8 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
@@ -166,7 +166,11 @@ class UDPPacket implements CDQEntry {
|
||||
int getMessageType() { return _messageType; }
|
||||
/** only for debugging and stats, does not go on the wire */
|
||||
void setMessageType(int type) { _messageType = type; }
|
||||
|
||||
/** only for debugging and stats */
|
||||
int getFragmentCount() { return _fragmentCount; }
|
||||
|
||||
/** only for debugging and stats */
|
||||
void setFragmentCount(int count) { _fragmentCount = count; }
|
||||
|
||||
RemoteHostId getRemoteHost() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user