propagate from branch 'i2p.i2p' (head 60a9a2297abeaf042645e3f0bc8d106f1ff585bf)

to branch 'i2p.i2p.zzz.test2' (head 6ff6f0bcee835d32aad62449a37f5171afde915a)
This commit is contained in:
zzz
2014-09-13 14:50:11 +00:00
158 changed files with 2982 additions and 947 deletions
@@ -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;
+19 -10
View File
@@ -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("&hellip;");
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)
+14 -29
View File
@@ -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);
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
@@ -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);
@@ -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