forked from I2P_Developers/i2p.i2p
I2CP:
- Add support for hostname lookups over I2CP with new HostLookup and HostReply messages. - Move username / password from CreateSession to GetDate for early authentication; this is an incompatible chage. Outside router context with authentication enabled, new clients will not work with old routers. Early authentication is not yet enforced, enable with i2cp.strictAuth=true. Will change default to true in a later release. - Block all actions before authentication. - Better disconnect messages to clients for diagnostics - Improve lookup command, add auth command in i2ptunnel CLI for testing - Don't start ClientWriterRunner thread in constructor - Don't flush in ClientWriterRunner unless necessary - Send GetDate even in SimpleSession outside of RouterContext - Improve SetDate wait logic to reduce locks and break out when Disconnect received - Add Disconnect handler to SimpleSession - I2Ping cleanups - Javadocs
This commit is contained in:
@ -335,12 +335,14 @@ class ClientConnectionRunner {
|
||||
* This is always bad.
|
||||
* See ClientMessageEventListener.handleCreateSession()
|
||||
* for why we don't send a SessionStatusMessage when we do this.
|
||||
* @param reason will be truncated to 255 bytes
|
||||
*/
|
||||
void disconnectClient(String reason) {
|
||||
disconnectClient(reason, Log.ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reason will be truncated to 255 bytes
|
||||
* @param logLevel e.g. Log.WARN
|
||||
* @since 0.8.2
|
||||
*/
|
||||
@ -351,6 +353,8 @@ class ClientConnectionRunner {
|
||||
+ " config: "
|
||||
+ _config);
|
||||
DisconnectMessage msg = new DisconnectMessage();
|
||||
if (reason.length() > 255)
|
||||
reason = reason.substring(0, 255);
|
||||
msg.setReason(reason);
|
||||
try {
|
||||
doSend(msg);
|
||||
|
@ -20,6 +20,7 @@ import net.i2p.data.i2cp.DestLookupMessage;
|
||||
import net.i2p.data.i2cp.DestroySessionMessage;
|
||||
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.GetDateMessage;
|
||||
import net.i2p.data.i2cp.HostLookupMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
@ -50,8 +51,11 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
protected final RouterContext _context;
|
||||
protected final ClientConnectionRunner _runner;
|
||||
private final boolean _enforceAuth;
|
||||
private volatile boolean _authorized;
|
||||
|
||||
private static final String PROP_AUTH = "i2cp.auth";
|
||||
/** if true, user/pw must be in GetDateMessage */
|
||||
private static final String PROP_AUTH_STRICT = "i2cp.strictAuth";
|
||||
|
||||
/**
|
||||
* @param enforceAuth set false for in-JVM, true for socket access
|
||||
@ -61,6 +65,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_log = _context.logManager().getLog(ClientMessageEventListener.class);
|
||||
_runner = runner;
|
||||
_enforceAuth = enforceAuth;
|
||||
if ((!_enforceAuth) || !_context.getBooleanProperty(PROP_AUTH))
|
||||
_authorized = true;
|
||||
_context.statManager().createRateStat("client.distributeTime", "How long it took to inject the client message into the router", "ClientMessages", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
@ -72,6 +78,20 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
if (_runner.isDead()) return;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message received: \n" + message);
|
||||
int type = message.getType();
|
||||
if (!_authorized) {
|
||||
// TODO change to default true
|
||||
boolean strict = _context.getBooleanProperty(PROP_AUTH_STRICT);
|
||||
if ((strict && type != GetDateMessage.MESSAGE_TYPE) ||
|
||||
(type != CreateSessionMessage.MESSAGE_TYPE &&
|
||||
type != GetDateMessage.MESSAGE_TYPE &&
|
||||
type != DestLookupMessage.MESSAGE_TYPE &&
|
||||
type != GetBandwidthLimitsMessage.MESSAGE_TYPE)) {
|
||||
_log.error("Received message type " + type + " without required authentication");
|
||||
_runner.disconnectClient("Authorization required");
|
||||
return;
|
||||
}
|
||||
}
|
||||
switch (message.getType()) {
|
||||
case GetDateMessage.MESSAGE_TYPE:
|
||||
handleGetDate((GetDateMessage)message);
|
||||
@ -103,6 +123,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
case DestLookupMessage.MESSAGE_TYPE:
|
||||
handleDestLookup((DestLookupMessage)message);
|
||||
break;
|
||||
case HostLookupMessage.MESSAGE_TYPE:
|
||||
handleHostLookup((HostLookupMessage)message);
|
||||
break;
|
||||
case ReconfigureSessionMessage.MESSAGE_TYPE:
|
||||
handleReconfigureSession((ReconfigureSessionMessage)message);
|
||||
break;
|
||||
@ -124,6 +147,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error occurred", error);
|
||||
// Is this is a little drastic for an unknown message type?
|
||||
// Send the whole exception string over for diagnostics
|
||||
_runner.disconnectClient(error.toString());
|
||||
_runner.stopRunning();
|
||||
}
|
||||
|
||||
@ -137,6 +162,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
String clientVersion = message.getVersion();
|
||||
if (clientVersion != null)
|
||||
_runner.setClientVersion(clientVersion);
|
||||
Properties props = message.getOptions();
|
||||
if (!checkAuth(props))
|
||||
return;
|
||||
try {
|
||||
// only send version if the client can handle it (0.8.7 or greater)
|
||||
_runner.doSend(new SetDateMessage(clientVersion != null ? CoreVersion.VERSION : null));
|
||||
@ -174,24 +202,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
}
|
||||
|
||||
// Auth, since 0.8.2
|
||||
if (_enforceAuth && _context.getBooleanProperty(PROP_AUTH)) {
|
||||
Properties props = in.getOptions();
|
||||
String user = props.getProperty("i2cp.username");
|
||||
String pw = props.getProperty("i2cp.password");
|
||||
if (user == null || user.length() == 0 || pw == null || pw.length() == 0) {
|
||||
_log.error("I2CP auth failed for client: " + props.getProperty("inbound.nickname"));
|
||||
_runner.disconnectClient("Authorization required to create session, specify i2cp.username and i2cp.password in session options");
|
||||
return;
|
||||
}
|
||||
PasswordManager mgr = new PasswordManager(_context);
|
||||
if (!mgr.checkHash(PROP_AUTH, user, pw)) {
|
||||
_log.error("I2CP auth failed for client: " + props.getProperty("inbound.nickname") + " user: " + user);
|
||||
_runner.disconnectClient("Authorization failed for Create Session, user = " + user);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2CP auth success for client: " + props.getProperty("inbound.nickname") + " user: " + user);
|
||||
}
|
||||
Properties inProps = in.getOptions();
|
||||
if (!checkAuth(inProps))
|
||||
return;
|
||||
|
||||
SessionId sessionId = new SessionId();
|
||||
sessionId.setSessionId(getNextSessionId());
|
||||
@ -213,6 +226,44 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
startCreateSessionJob();
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect - sets _authorized.
|
||||
* Side effect - disconnects session if not authorized.
|
||||
*
|
||||
* @param props contains i2cp.username and i2cp.password, may be null
|
||||
* @return success
|
||||
* @since 0.9.10
|
||||
*/
|
||||
private boolean checkAuth(Properties props) {
|
||||
if (_authorized)
|
||||
return true;
|
||||
if (_enforceAuth && _context.getBooleanProperty(PROP_AUTH)) {
|
||||
String user = null;
|
||||
String pw = null;
|
||||
if (props != null) {
|
||||
user = props.getProperty("i2cp.username");
|
||||
pw = props.getProperty("i2cp.password");
|
||||
}
|
||||
if (user == null || user.length() == 0 || pw == null || pw.length() == 0) {
|
||||
_log.error("I2CP auth failed");
|
||||
_runner.disconnectClient("Authorization required, specify i2cp.username and i2cp.password in options");
|
||||
_authorized = false;
|
||||
return false;
|
||||
}
|
||||
PasswordManager mgr = new PasswordManager(_context);
|
||||
if (!mgr.checkHash(PROP_AUTH, user, pw)) {
|
||||
_log.error("I2CP auth failed user: " + user);
|
||||
_runner.disconnectClient("Authorization failed, user = " + user);
|
||||
_authorized = false;
|
||||
return false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2CP auth success user: " + user);
|
||||
}
|
||||
_authorized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override for testing
|
||||
* @since 0.9.8
|
||||
@ -315,6 +366,15 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash()));
|
||||
}
|
||||
|
||||
/**
|
||||
* override for testing
|
||||
* @since 0.9.10
|
||||
*/
|
||||
protected void handleHostLookup(HostLookupMessage message) {
|
||||
_context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getReqID(),
|
||||
message.getTimeout(), message.getHash(), message.getHostname()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Message's Session ID ignored. This doesn't support removing previously set options.
|
||||
* Nor do we bother with message.getSessionConfig().verifySignature() ... should we?
|
||||
|
@ -4,32 +4,83 @@
|
||||
*/
|
||||
package net.i2p.router.client;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.i2cp.DestReplyMessage;
|
||||
import net.i2p.data.i2cp.HostReplyMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/**
|
||||
* Look up the lease of a hash, to convert it to a Destination for the client
|
||||
* Look up the lease of a hash, to convert it to a Destination for the client.
|
||||
* Or, since 0.9.10, lookup a host name in the naming service.
|
||||
*/
|
||||
class LookupDestJob extends JobImpl {
|
||||
private final ClientConnectionRunner _runner;
|
||||
private final long _reqID;
|
||||
private final long _timeout;
|
||||
private final Hash _hash;
|
||||
private final String _name;
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = 15*1000;
|
||||
|
||||
public LookupDestJob(RouterContext context, ClientConnectionRunner runner, Hash h) {
|
||||
this(context, runner, -1, DEFAULT_TIMEOUT, h, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* One of h or name non-null
|
||||
* @param reqID must be >= 0 if name != null
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public LookupDestJob(RouterContext context, ClientConnectionRunner runner,
|
||||
long reqID, long timeout, Hash h, String name) {
|
||||
super(context);
|
||||
if ((h == null && name == null) ||
|
||||
(h != null && name != null) ||
|
||||
(reqID < 0 && name != null))
|
||||
throw new IllegalArgumentException();
|
||||
_runner = runner;
|
||||
_reqID = reqID;
|
||||
_timeout = timeout;
|
||||
if (name != null && name.length() == 60) {
|
||||
// convert a b32 lookup to a hash lookup
|
||||
String nlc = name.toLowerCase(Locale.US);
|
||||
if (nlc.endsWith(".b32.i2p")) {
|
||||
byte[] b = Base32.decode(nlc.substring(0, 52));
|
||||
if (b != null && b.length == Hash.HASH_LENGTH) {
|
||||
h = Hash.create(b);
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
_hash = h;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public String getName() { return "LeaseSet Lookup for Client"; }
|
||||
public String getName() { return _name != null ?
|
||||
"HostName Lookup for Client" :
|
||||
"LeaseSet Lookup for Client";
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
DoneJob done = new DoneJob(getContext());
|
||||
// TODO add support for specifying the timeout in the lookup message
|
||||
getContext().netDb().lookupLeaseSet(_hash, done, done, 15*1000);
|
||||
if (_name != null) {
|
||||
// inline, ignore timeout
|
||||
Destination d = getContext().namingService().lookup(_name);
|
||||
if (d != null)
|
||||
returnDest(d);
|
||||
else
|
||||
returnFail();
|
||||
} else {
|
||||
DoneJob done = new DoneJob(getContext());
|
||||
getContext().netDb().lookupLeaseSet(_hash, done, done, _timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private class DoneJob extends JobImpl {
|
||||
@ -42,12 +93,16 @@ class LookupDestJob extends JobImpl {
|
||||
if (ls != null)
|
||||
returnDest(ls.getDestination());
|
||||
else
|
||||
returnHash(_hash);
|
||||
returnFail();
|
||||
}
|
||||
}
|
||||
|
||||
private void returnDest(Destination d) {
|
||||
DestReplyMessage msg = new DestReplyMessage(d);
|
||||
I2CPMessage msg;
|
||||
if (_reqID >= 0)
|
||||
msg = new HostReplyMessage(d, _reqID);
|
||||
else
|
||||
msg = new DestReplyMessage(d);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
} catch (I2CPMessageException ime) {}
|
||||
@ -57,8 +112,12 @@ class LookupDestJob extends JobImpl {
|
||||
* Return the failed hash so the client can correlate replies with requests
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private void returnHash(Hash h) {
|
||||
DestReplyMessage msg = new DestReplyMessage(h);
|
||||
private void returnFail() {
|
||||
I2CPMessage msg;
|
||||
if (_reqID >= 0)
|
||||
msg = new HostReplyMessage(HostReplyMessage.RESULT_FAILURE, _reqID);
|
||||
else
|
||||
msg = new DestReplyMessage(_hash);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
} catch (I2CPMessageException ime) {}
|
||||
|
Reference in New Issue
Block a user