- 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:
zzz
2013-12-21 00:21:48 +00:00
parent 38c02b44b9
commit cc97a19d3c
18 changed files with 1093 additions and 130 deletions

View File

@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
@ -57,6 +58,8 @@ import net.i2p.I2PException;
import net.i2p.client.I2PClient; import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession; import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSimpleClient;
import net.i2p.client.naming.NamingService; import net.i2p.client.naming.NamingService;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
@ -68,6 +71,7 @@ import net.i2p.i2ptunnel.streamr.StreamrConsumer;
import net.i2p.i2ptunnel.streamr.StreamrProducer; import net.i2p.i2ptunnel.streamr.StreamrProducer;
import net.i2p.util.EventDispatcherImpl; import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
/** /**
* An I2PTunnel tracks one or more I2PTunnelTasks and one or more I2PSessions. * An I2PTunnel tracks one or more I2PTunnelTasks and one or more I2PSessions.
@ -87,9 +91,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
public boolean ownDest = false; public boolean ownDest = false;
/** the I2CP port */ /** the I2CP port, non-null */
public String port = System.getProperty(I2PClient.PROP_TCP_PORT, "7654"); public String port = System.getProperty(I2PClient.PROP_TCP_PORT, "7654");
/** the I2CP host */ /** the I2CP host, non-null */
public String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); public String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
/** the listen-on host. Sadly the listen-on port does not have a field. */ /** the listen-on host. Sadly the listen-on port does not have a field. */
public String listenHost = host; public String listenHost = host;
@ -168,7 +172,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
System.out.println("Enter 'help' for help."); System.out.println("Enter 'help' for help.");
BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
while (true) { while (true) {
System.out.print("I2PTunnel>"); System.out.print("I2PTunnel> ");
String cmd = r.readLine(); String cmd = r.readLine();
if (cmd == null) break; if (cmd == null) break;
if (cmd.length() <= 0) continue; if (cmd.length() <= 0) continue;
@ -293,6 +297,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
runPing(allargs, l); runPing(allargs, l);
} else if (cmdname.equals("owndest")) { } else if (cmdname.equals("owndest")) {
runOwnDest(args, l); runOwnDest(args, l);
} else if (cmdname.equals("auth")) {
runAuth(args, l);
} else { } else {
l.log("Unknown command [" + cmdname + "]"); l.log("Unknown command [" + cmdname + "]");
} }
@ -308,27 +314,28 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
private static void runHelp(Logging l) { private static void runHelp(Logging l) {
l.log("Command list:"); l.log("Command list:");
// alphabetical please... // alphabetical please...
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]"); l.log(" auth <username> <password>");
l.log("clientoptions[ key=value]*"); l.log(" client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("close [forced] <jobnumber>|all"); l.log(" clientoptions [key=value ]*");
l.log("config <i2phost> <i2pport>"); l.log(" close [forced] <jobnumber>|all");
l.log("connectclient <port> [<sharedClient>] [<proxy>]"); l.log(" config [-s] <i2phost> <i2pport>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]"); l.log(" connectclient <port> [<sharedClient>] [<proxy>]");
l.log("gentextkeys"); l.log(" genkeys <privkeyfile> [<pubkeyfile>]");
l.log("httpbidirserver <host> <port> <proxyport> <spoofedhost> <privkeyfile>"); l.log(" gentextkeys");
l.log("httpclient <port> [<sharedClient>] [<proxy>]"); l.log(" httpbidirserver <host> <port> <proxyport> <spoofedhost> <privkeyfile>");
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>"); l.log(" httpclient <port> [<sharedClient>] [<proxy>]");
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]"); l.log(" httpserver <host> <port> <spoofedhost> <privkeyfile>");
l.log("list"); l.log(" ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("listen_on <ip>"); l.log(" list");
l.log("lookup <name>"); l.log(" listen_on <ip>");
l.log("owndest yes|no"); l.log(" lookup <name>");
l.log("ping <args>"); l.log(" owndest yes|no");
l.log("quit"); l.log(" ping <args>");
l.log("read_timeout <msecs>"); l.log(" quit");
l.log("run <commandfile>"); l.log(" read_timeout <msecs>");
l.log("server <host> <port> <privkeyfile>"); l.log(" run <commandfile>");
l.log("textserver <host> <port> <privkey>"); l.log(" server <host> <port> <privkeyfile>");
l.log(" textserver <host> <port> <privkey>");
} }
/** /**
@ -345,15 +352,43 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
* @param l logger to receive events and output * @param l logger to receive events and output
*/ */
public void runClientOptions(String args[], Logging l) { public void runClientOptions(String args[], Logging l) {
_clientOptions.clear(); if (args != null && args.length > 0) {
if (args != null) { int i = 0;
for (int i = 0; i < args.length; i++) { if (args[0].equals("-a")) {
i++;
} else if (args[0].equals("-c")) {
_clientOptions.clear();
l.log("Client options cleared");
return;
} else if (args[0].equals("-x")) {
i++;
for ( ; i < args.length; i++) {
if (_clientOptions.remove(args[i]) != null)
l.log("Removed " + args[i]);
}
return;
} else {
_clientOptions.clear();
}
for ( ; i < args.length; i++) {
int index = args[i].indexOf('='); int index = args[i].indexOf('=');
if (index <= 0) continue; if (index <= 0) continue;
String key = args[i].substring(0, index); String key = args[i].substring(0, index);
String val = args[i].substring(index+1); String val = args[i].substring(index+1);
_clientOptions.setProperty(key, val); _clientOptions.setProperty(key, val);
} }
} else {
l.log("Usage:");
l.log(" clientoptions [key=value ]* // sets current options");
l.log(" clientoptions -a [key=value ]* // adds to current options");
l.log(" clientoptions -c // clears current options");
l.log(" clientoptions -x [key ]* // removes listed options");
l.log("Current options:");
Properties p = new OrderedProperties();
p.putAll(_clientOptions);
for (Map.Entry<Object, Object> e : p.entrySet()) {
l.log(" [" + e.getKey() + "] = [" + e.getValue() + ']');
}
} }
notifyEvent("clientoptions_onResult", "ok"); notifyEvent("clientoptions_onResult", "ok");
} }
@ -1147,18 +1182,47 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
* @param l logger to receive events and output * @param l logger to receive events and output
*/ */
private void runConfig(String args[], Logging l) { private void runConfig(String args[], Logging l) {
if (args.length == 2) { if (args.length >= 2) {
host = args[0]; int i = 0;
if (args[0].equals("-s")) {
_clientOptions.setProperty("i2cp.SSL", "true");
i++;
} else {
_clientOptions.remove("i2cp.SSL");
}
host = args[i++];
listenHost = host; listenHost = host;
port = args[1]; port = args[i];
notifyEvent("configResult", "ok"); notifyEvent("configResult", "ok");
} else { } else {
l.log("config <i2phost> <i2pport>"); l.log("Usage:");
l.log(" config [-s] <i2phost> <i2pport>");
l.log(" sets the connection to the i2p router."); l.log(" sets the connection to the i2p router.");
l.log("Current setting:");
boolean ssl = Boolean.parseBoolean(_clientOptions.getProperty("i2cp.SSL"));
l.log(" " + host + ' ' + port + (ssl ? " SSL" : ""));
notifyEvent("configResult", "error"); notifyEvent("configResult", "error");
} }
} }
/**
* Specify the i2cp username and password
*
* @param args {username, password}
* @param l logger to receive events and output
* @since 0.9.10
*/
private void runAuth(String args[], Logging l) {
if (args.length == 2) {
_clientOptions.setProperty("i2cp.username", args[0]);
_clientOptions.setProperty("i2cp.password", args[1]);
} else {
l.log("Usage:");
l.log(" auth <username> <password>");
l.log(" Sets the i2cp credentials");
}
}
/** /**
* Specify whether to use its own destination for each outgoing tunnel * Specify whether to use its own destination for each outgoing tunnel
* Deprecated - only used by CLI * Deprecated - only used by CLI
@ -1415,16 +1479,19 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
notifyEvent("lookupResult", "invalidUsage"); notifyEvent("lookupResult", "invalidUsage");
} else { } else {
try { try {
Destination dest = destFromName(args[0]); boolean ssl = Boolean.parseBoolean(_clientOptions.getProperty("i2cp.SSL"));
String user = _clientOptions.getProperty("i2cp.username");
String pw = _clientOptions.getProperty("i2cp.password");
Destination dest = destFromName(args[0], host, port, ssl, user, pw);
if (dest == null) { if (dest == null) {
l.log("Unknown host"); l.log("Unknown host: " + args[0]);
notifyEvent("lookupResult", "unkown host"); notifyEvent("lookupResult", "unkown host");
} else { } else {
l.log(dest.toBase64()); l.log(dest.toBase64());
notifyEvent("lookupResult", dest.toBase64()); notifyEvent("lookupResult", dest.toBase64());
} }
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
l.log("Unknown or invalid host"); l.log("Unknown or invalid host: " + args[0]);
notifyEvent("lookupResult", "invalid host"); notifyEvent("lookupResult", "invalid host");
} }
} }
@ -1599,6 +1666,19 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
* @deprecated Don't use i2ptunnel for lookup! Use I2PAppContext.getGlobalContext().namingService().lookup(name) from i2p.jar * @deprecated Don't use i2ptunnel for lookup! Use I2PAppContext.getGlobalContext().namingService().lookup(name) from i2p.jar
*/ */
public static Destination destFromName(String name) throws DataFormatException { public static Destination destFromName(String name) throws DataFormatException {
return destFromName(name, null, null, false, null, null);
}
/**
* @param i2cpHost may be null
* @param i2cpPort may be null
* @param user may be null
* @param pw may be null
* @since 0.9.10
*/
private static Destination destFromName(String name, String i2cpHost,
String i2cpPort, boolean isSSL,
String user, String pw) throws DataFormatException {
if ((name == null) || (name.trim().length() <= 0)) throw new DataFormatException("Empty destination provided"); if ((name == null) || (name.trim().length() <= 0)) throw new DataFormatException("Empty destination provided");
@ -1642,8 +1722,46 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
} }
} else { } else {
// ask naming service // ask naming service
name = name.trim();
NamingService inst = ctx.namingService(); NamingService inst = ctx.namingService();
return inst.lookup(name); boolean b32 = name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p");
Destination d = null;
if (ctx.isRouterContext() || !b32) {
// Local lookup.
// Even though we could do b32 outside router ctx here,
// we do it below instead so we can set the host and port,
// which we can't do with lookup()
d = inst.lookup(name);
if (d != null || ctx.isRouterContext())
return d;
}
// Outside router context only,
// try simple session to ask the router.
I2PClient client = new I2PSimpleClient();
Properties opts = new Properties();
if (i2cpHost != null)
opts.put(I2PClient.PROP_TCP_HOST, i2cpHost);
if (i2cpPort != null)
opts.put(I2PClient.PROP_TCP_PORT, i2cpPort);
opts.put("i2cp.SSL", Boolean.toString(isSSL));
if (user != null)
opts.put("i2cp.username", user);
if (pw != null)
opts.put("i2cp.password", pw);
I2PSession session = null;
try {
session = client.createSession(null, opts);
session.connect();
d = session.lookupDest(name);
} catch (I2PSessionException ise) {
if (log.shouldLog(Log.WARN))
log.warn("Lookup via router failed", ise);
} finally {
if (session != null) {
try { session.destroySession(); } catch (I2PSessionException ise) {}
}
}
return d;
} }
} }

View File

@ -18,7 +18,7 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.Log; import net.i2p.util.Log;
public class I2Ping extends I2PTunnelTask implements Runnable { public class I2Ping extends I2PTunnelTask implements Runnable {
private final static Log _log = new Log(I2Ping.class); private final Log _log = new Log(I2Ping.class);
private int PING_COUNT = 3; private int PING_COUNT = 3;
private static final int CPING_COUNT = 5; private static final int CPING_COUNT = 5;
@ -28,20 +28,20 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
private int MAX_SIMUL_PINGS = 10; // not really final... private int MAX_SIMUL_PINGS = 10; // not really final...
private boolean countPing = false; private boolean countPing;
private boolean reportTimes = true; private boolean reportTimes = true;
private I2PSocketManager sockMgr; private final I2PSocketManager sockMgr;
private Logging l; private final Logging l;
private boolean finished = false; private boolean finished;
private String command; private final String command;
private long timeout = PING_TIMEOUT; private long timeout = PING_TIMEOUT;
private final Object simulLock = new Object(); private final Object simulLock = new Object();
private int simulPings = 0; private int simulPings;
private long lastPingTime = 0; private long lastPingTime;
private final Object lock = new Object(), slock = new Object(); private final Object lock = new Object();
//public I2Ping(String cmd, Logging l, //public I2Ping(String cmd, Logging l,
// boolean ownDest) { // boolean ownDest) {
@ -52,12 +52,10 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
super("I2Ping [" + cmd + "]", notifyThis, tunnel); super("I2Ping [" + cmd + "]", notifyThis, tunnel);
this.l = l; this.l = l;
command = cmd; command = cmd;
synchronized (slock) { if (ownDest) {
if (ownDest) { sockMgr = I2PTunnelClient.buildSocketManager(tunnel);
sockMgr = I2PTunnelClient.buildSocketManager(tunnel); } else {
} else { sockMgr = I2PTunnelClient.getSocketManager(tunnel);
sockMgr = I2PTunnelClient.getSocketManager(tunnel);
}
} }
Thread t = new I2PAppThread(this); Thread t = new I2PAppThread(this);
t.setName("Client"); t.setName("Client");
@ -187,7 +185,7 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
} }
public class PingHandler extends I2PAppThread { public class PingHandler extends I2PAppThread {
private String destination; private final String destination;
public PingHandler(String dest) { public PingHandler(String dest) {
this.destination = dest; this.destination = dest;

View File

@ -8,10 +8,12 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext;
import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.internal.PoisonI2CPMessage; import net.i2p.internal.PoisonI2CPMessage;
import net.i2p.util.I2PAppThread; import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/** /**
* Copied from net.i2p.router.client * Copied from net.i2p.router.client
@ -25,15 +27,24 @@ class ClientWriterRunner implements Runnable {
private final I2PSessionImpl _session; private final I2PSessionImpl _session;
private final BlockingQueue<I2CPMessage> _messagesToWrite; private final BlockingQueue<I2CPMessage> _messagesToWrite;
private static final AtomicLong __Id = new AtomicLong(); private static final AtomicLong __Id = new AtomicLong();
//private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ClientWriterRunner.class);
private static final int MAX_QUEUE_SIZE = 32; private static final int MAX_QUEUE_SIZE = 32;
private static final long MAX_SEND_WAIT = 10*1000; private static final long MAX_SEND_WAIT = 10*1000;
/** starts the thread too */ /**
* As of 0.9.10 does not start the thread, caller must call startWriting()
*/
public ClientWriterRunner(OutputStream out, I2PSessionImpl session) { public ClientWriterRunner(OutputStream out, I2PSessionImpl session) {
_out = new BufferedOutputStream(out); _out = new BufferedOutputStream(out);
_session = session; _session = session;
_messagesToWrite = new LinkedBlockingQueue<I2CPMessage>(MAX_QUEUE_SIZE); _messagesToWrite = new LinkedBlockingQueue<I2CPMessage>(MAX_QUEUE_SIZE);
}
/**
* @since 0.9.10
*/
public void startWriting() {
Thread t = new I2PAppThread(this, "I2CP Client Writer " + __Id.incrementAndGet(), true); Thread t = new I2PAppThread(this, "I2CP Client Writer " + __Id.incrementAndGet(), true);
t.start(); t.start();
} }
@ -76,7 +87,8 @@ class ClientWriterRunner implements Runnable {
// only thread, we don't need synchronized // only thread, we don't need synchronized
try { try {
msg.writeMessage(_out); msg.writeMessage(_out);
_out.flush(); if (_messagesToWrite.isEmpty())
_out.flush();
} catch (I2CPMessageException ime) { } catch (I2CPMessageException ime) {
_session.propogateError("Error writing out the message", ime); _session.propogateError("Error writing out the message", ime);
_session.disconnect(); _session.disconnect();

View File

@ -0,0 +1,38 @@
package net.i2p.client;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
import net.i2p.I2PAppContext;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.HostReplyMessage;
import net.i2p.util.Log;
import net.i2p.data.Destination;
/**
* Handle I2CP dest replies from the router
*
* @since 0.9.10
*/
class HostReplyMessageHandler extends HandlerImpl {
public HostReplyMessageHandler(I2PAppContext ctx) {
super(ctx, HostReplyMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle message " + message);
HostReplyMessage msg = (HostReplyMessage) message;
Destination d = msg.getDestination();
long id = msg.getReqID();
if (d != null) {
session.destReceived(id, d);
} else {
session.destLookupFailed(id);
}
}
}

View File

@ -13,6 +13,7 @@ import net.i2p.I2PAppContext;
import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.BandwidthLimitsMessage;
import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.DestReplyMessage;
import net.i2p.data.i2cp.DisconnectMessage; import net.i2p.data.i2cp.DisconnectMessage;
import net.i2p.data.i2cp.HostReplyMessage;
import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.i2cp.RequestLeaseSetMessage; import net.i2p.data.i2cp.RequestLeaseSetMessage;
@ -40,6 +41,7 @@ class I2PClientMessageHandlerMap {
highest = Math.max(highest, MessageStatusMessage.MESSAGE_TYPE); highest = Math.max(highest, MessageStatusMessage.MESSAGE_TYPE);
highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE); highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE);
highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE); highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE);
highest = Math.max(highest, HostReplyMessage.MESSAGE_TYPE);
highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE); highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE);
highest = Math.max(highest, RequestVariableLeaseSetMessage.MESSAGE_TYPE); highest = Math.max(highest, RequestVariableLeaseSetMessage.MESSAGE_TYPE);
@ -53,6 +55,7 @@ class I2PClientMessageHandlerMap {
_handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
_handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
_handlers[RequestVariableLeaseSetMessage.MESSAGE_TYPE] = new RequestVariableLeaseSetMessageHandler(context); _handlers[RequestVariableLeaseSetMessage.MESSAGE_TYPE] = new RequestVariableLeaseSetMessageHandler(context);
_handlers[HostReplyMessage.MESSAGE_TYPE] = new HostReplyMessageHandler(context);
} }
public I2CPMessageHandler getHandler(int messageTypeId) { public I2CPMessageHandler getHandler(int messageTypeId) {

View File

@ -214,6 +214,7 @@ public interface I2PSession {
public Destination lookupDest(Hash h) throws I2PSessionException; public Destination lookupDest(Hash h) throws I2PSessionException;
/** /**
* Lookup a Destination by Hash.
* Blocking. * Blocking.
* @param maxWait ms * @param maxWait ms
* @since 0.8.3 * @since 0.8.3
@ -221,6 +222,68 @@ public interface I2PSession {
*/ */
public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException; public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException;
/**
* Ask the router to lookup a Destination by host name.
* Blocking. Waits a max of 10 seconds by default.
*
* This only makes sense for a b32 hostname, OR outside router context.
* Inside router context, just query the naming service.
* Outside router context, this does NOT query the context naming service.
* Do that first if you expect a local addressbook.
*
* This will log a warning for non-b32 in router context.
*
* Suggested implementation:
*
*<pre>
* if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p")) {
* if (session != null)
* return session.lookup(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))));
* else
* return ctx.namingService().lookup(name); // simple session for xxx.b32.i2p handled by naming service (optional if you need lookup w/o an existing session)
* } else if (ctx.isRouterContext()) {
* return ctx.namingService().lookup(name); // hostname from router's naming service
* } else {
* Destination d = ctx.namingService().lookup(name); // local naming svc, optional
* if (d != null)
* return d;
* if (session != null)
* return session.lookup(name);
* // simple session (optional if you need lookup w/o an existing session)
* Destination rv = null;
* I2PClient client = new I2PSimpleClient();
* Properties opts = new Properties();
* opts.put(I2PClient.PROP_TCP_HOST, host);
* opts.put(I2PClient.PROP_TCP_PORT, port);
* I2PSession session = null;
* try {
* session = client.createSession(null, opts);
* session.connect();
* rv = session.lookupDest(name);
* } finally {
* if (session != null)
* session.destroySession();
* }
* return rv;
* }
*</pre>
*
* Requires router side to be 0.9.10 or higher. If the router is older,
* this will return null immediately.
*
* @since 0.9.10
*/
public Destination lookupDest(String name) throws I2PSessionException;
/**
* Ask the router to lookup a Destination by host name.
* Blocking. See above for details.
* @param maxWait ms
* @since 0.9.10
* @return null on failure
*/
public Destination lookupDest(String name, long maxWait) throws I2PSessionException;
/** /**
* Pass updated options to the router. * Pass updated options to the router.
* Does not remove properties previously present but missing from this options parameter. * Does not remove properties previously present but missing from this options parameter.

View File

@ -19,13 +19,16 @@ import java.net.UnknownHostException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.CoreVersion; import net.i2p.CoreVersion;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash; import net.i2p.data.Hash;
@ -35,6 +38,7 @@ import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2cp.DestLookupMessage; import net.i2p.data.i2cp.DestLookupMessage;
import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
import net.i2p.data.i2cp.GetDateMessage; import net.i2p.data.i2cp.GetDateMessage;
import net.i2p.data.i2cp.HostLookupMessage;
import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.MessagePayloadMessage;
@ -46,6 +50,7 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.LHMCache; import net.i2p.util.LHMCache;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer;
import net.i2p.util.VersionComparator; import net.i2p.util.VersionComparator;
@ -98,12 +103,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** hashes of lookups we are waiting for */ /** hashes of lookups we are waiting for */
protected final LinkedBlockingQueue<LookupWaiter> _pendingLookups = new LinkedBlockingQueue<LookupWaiter>(); protected final LinkedBlockingQueue<LookupWaiter> _pendingLookups = new LinkedBlockingQueue<LookupWaiter>();
private final AtomicInteger _lookupID = new AtomicInteger();
protected final Object _bwReceivedLock = new Object(); protected final Object _bwReceivedLock = new Object();
protected volatile int[] _bwLimits; protected volatile int[] _bwLimits;
protected final I2PClientMessageHandlerMap _handlerMap; protected final I2PClientMessageHandlerMap _handlerMap;
/** used to seperate things out so we can get rid of singletons */ /** used to separate things out so we can get rid of singletons */
protected final I2PAppContext _context; protected final I2PAppContext _context;
/** monitor for waiting until a lease set has been granted */ /** monitor for waiting until a lease set has been granted */
@ -114,6 +120,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*/ */
protected enum State { protected enum State {
OPENING, OPENING,
/** @since 0.9.10 */
GOTDATE,
OPEN, OPEN,
CLOSING, CLOSING,
CLOSED CLOSED
@ -122,11 +130,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private State _state = State.CLOSED; private State _state = State.CLOSED;
protected final Object _stateLock = new Object(); protected final Object _stateLock = new Object();
/** have we received the current date from the router yet? */
private volatile boolean _dateReceived;
/** lock that we wait upon, that the SetDateMessageHandler notifies */
private final Object _dateReceivedLock = new Object();
/** /**
* thread that we tell when new messages are available who then tells us * thread that we tell when new messages are available who then tells us
* to fetch them. The point of this is so that the fetch doesn't block the * to fetch them. The point of this is so that the fetch doesn't block the
@ -139,14 +142,20 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private boolean _isReduced; private boolean _isReduced;
private final boolean _fastReceive; private final boolean _fastReceive;
private volatile boolean _routerSupportsFastReceive; private volatile boolean _routerSupportsFastReceive;
private volatile boolean _routerSupportsHostLookup;
/** /**
* Since 0.9.10, key is either a Hash or a String
* @since 0.8.9 * @since 0.8.9
*/ */
private static final Map<Hash, Destination> _lookupCache = new LHMCache<Hash, Destination>(16); private static final Map<Object, Destination> _lookupCache = new LHMCache<Object, Destination>(16);
private static final String MIN_HOST_LOOKUP_VERSION = "0.9.10";
private static final boolean TEST_LOOKUP = false;
/** SSL interface (only) @since 0.8.3 */ /** SSL interface (only) @since 0.8.3 */
protected static final String PROP_ENABLE_SSL = "i2cp.SSL"; protected static final String PROP_ENABLE_SSL = "i2cp.SSL";
protected static final String PROP_USER = "i2cp.username";
protected static final String PROP_PW = "i2cp.password";
private static final long VERIFY_USAGE_TIME = 60*1000; private static final long VERIFY_USAGE_TIME = 60*1000;
@ -159,9 +168,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_routerSupportsFastReceive = _context.isRouterContext() || _routerSupportsFastReceive = _context.isRouterContext() ||
(routerVersion != null && routerVersion.length() > 0 && (routerVersion != null && routerVersion.length() > 0 &&
VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0); VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0);
_dateReceived = true; _routerSupportsHostLookup = _context.isRouterContext() ||
synchronized (_dateReceivedLock) { TEST_LOOKUP ||
_dateReceivedLock.notifyAll(); (routerVersion != null && routerVersion.length() > 0 &&
VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0);
synchronized (_stateLock) {
if (_state == State.OPENING) {
_state = State.GOTDATE;
_stateLock.notifyAll();
}
} }
} }
@ -205,6 +220,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_privateKey = null; _privateKey = null;
_signingPrivateKey = null; _signingPrivateKey = null;
} }
_routerSupportsFastReceive = _context.isRouterContext();
_routerSupportsHostLookup = _context.isRouterContext();
} }
/** /**
@ -239,12 +256,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
// auto-add auth if required, not set in the options, and we are not in the same JVM // auto-add auth if required, not set in the options, and we are not in the same JVM
if ((!_context.isRouterContext()) && if ((!_context.isRouterContext()) &&
_context.getBooleanProperty("i2cp.auth") && _context.getBooleanProperty("i2cp.auth") &&
((!opts.containsKey("i2cp.username")) || (!opts.containsKey("i2cp.password")))) { ((!opts.containsKey(PROP_USER)) || (!opts.containsKey(PROP_PW)))) {
String configUser = _context.getProperty("i2cp.username"); String configUser = _context.getProperty(PROP_USER);
String configPW = _context.getProperty("i2cp.password"); String configPW = _context.getProperty(PROP_PW);
if (configUser != null && configPW != null) { if (configUser != null && configPW != null) {
options.setProperty("i2cp.username", configUser); options.setProperty(PROP_USER, configUser);
options.setProperty("i2cp.password", configPW); options.setProperty(PROP_PW, configPW);
} }
} }
if (options.getProperty(I2PClient.PROP_FAST_RECEIVE) == null) if (options.getProperty(I2PClient.PROP_FAST_RECEIVE) == null)
@ -399,6 +416,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
loop = false; loop = false;
break; break;
case OPENING: case OPENING:
case GOTDATE:
wasOpening = true; wasOpening = true;
try { try {
_stateLock.wait(10*1000); _stateLock.wait(10*1000);
@ -455,6 +473,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
out.write(I2PClient.PROTOCOL_BYTE); out.write(I2PClient.PROTOCOL_BYTE);
out.flush(); out.flush();
_writer = new ClientWriterRunner(out, this); _writer = new ClientWriterRunner(out, this);
_writer.startWriting();
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE); InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
_reader = new I2CPMessageReader(in, this); _reader = new I2CPMessageReader(in, this);
} }
@ -462,26 +481,23 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading"); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading");
_reader.startReading(); _reader.startReading();
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate"); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate");
sendMessage(new GetDateMessage(CoreVersion.VERSION)); Properties auth = null;
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response"); if ((!_context.isRouterContext()) && _options.containsKey(PROP_USER) && _options.containsKey(PROP_PW)) {
int waitcount = 0; // Only supported by routers 0.9.10 or higher, but we don't know the version yet.
while (!_dateReceived) { // Auth will also be sent in the SessionConfig.
if (waitcount++ > 30) { auth = new OrderedProperties();
throw new IOException("No handshake received from the router"); auth.setProperty(PROP_USER, _options.getProperty(PROP_USER));
} auth.setProperty(PROP_PW, _options.getProperty(PROP_PW));
synchronized (_dateReceivedLock) {
// InterruptedException caught below
_dateReceivedLock.wait(1000);
}
} }
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response"); sendMessage(new GetDateMessage(CoreVersion.VERSION, auth));
waitForDate();
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before producer.connect()"); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before producer.connect()");
_producer.connect(this); _producer.connect(this);
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After producer.connect()"); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After producer.connect()");
// wait until we have created a lease set // wait until we have created a lease set
waitcount = 0; int waitcount = 0;
while (_leaseSet == null) { while (_leaseSet == null) {
if (waitcount++ > 5*60) { if (waitcount++ > 5*60) {
throw new IOException("No tunnels built after waiting 5 minutes. Your network connection may be down, or there is severe network congestion."); throw new IOException("No tunnels built after waiting 5 minutes. Your network connection may be down, or there is severe network congestion.");
@ -524,6 +540,28 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
} }
} }
/**
* @since 0.9.10 moved from connect()
*/
protected void waitForDate() throws InterruptedException, IOException {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response");
int waitcount = 0;
while (true) {
if (waitcount++ > 30) {
throw new IOException("No handshake received from the router");
}
synchronized(_stateLock) {
if (_state == State.GOTDATE)
break;
if (_state != State.OPENING)
throw new IOException("Socket closed");
// InterruptedException caught by caller
_stateLock.wait(1000);
}
}
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response");
}
/** /**
* Pull the unencrypted data from the message that we've already prefetched and * Pull the unencrypted data from the message that we've already prefetched and
* notified the user that its available. * notified the user that its available.
@ -890,13 +928,16 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* Will interrupt a connect in progress. * Will interrupt a connect in progress.
*/ */
protected void disconnect() { protected void disconnect() {
State oldState;
synchronized(_stateLock) { synchronized(_stateLock) {
if (_state == State.CLOSING || _state == State.CLOSED) if (_state == State.CLOSING || _state == State.CLOSED)
return; return;
oldState = _state;
changeState(State.CLOSING); changeState(State.CLOSING);
} }
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnect() called", new Exception("Disconnect")); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnect() called", new Exception("Disconnect"));
if (shouldReconnect()) { // don't try to reconnect if it failed before GETTDATE
if (oldState != State.OPENING && shouldReconnect()) {
if (reconnect()) { if (reconnect()) {
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "I2CP reconnection successful"); if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "I2CP reconnection successful");
return; return;
@ -963,14 +1004,17 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
return buf.toString(); return buf.toString();
} }
/** called by the message handler */ /**
* Called by the message handler
* on reception of DestReplyMessage
*/
void destReceived(Destination d) { void destReceived(Destination d) {
Hash h = d.calculateHash(); Hash h = d.calculateHash();
synchronized (_lookupCache) { synchronized (_lookupCache) {
_lookupCache.put(h, d); _lookupCache.put(h, d);
} }
for (LookupWaiter w : _pendingLookups) { for (LookupWaiter w : _pendingLookups) {
if (w.hash.equals(h)) { if (h.equals(w.hash)) {
w.destination = d; w.destination = d;
synchronized (w) { synchronized (w) {
w.notifyAll(); w.notifyAll();
@ -979,10 +1023,52 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
} }
} }
/** called by the message handler */ /**
* Called by the message handler
* on reception of DestReplyMessage
*/
void destLookupFailed(Hash h) { void destLookupFailed(Hash h) {
for (LookupWaiter w : _pendingLookups) { for (LookupWaiter w : _pendingLookups) {
if (w.hash.equals(h)) { if (h.equals(w.hash)) {
synchronized (w) {
w.notifyAll();
}
}
}
}
/**
* Called by the message handler
* on reception of HostReplyMessage
* @since 0.9.10
*/
void destReceived(long nonce, Destination d) {
// notify by hash
destReceived(d);
// notify by nonce
for (LookupWaiter w : _pendingLookups) {
if (nonce == w.nonce) {
w.destination = d;
if (w.name != null) {
synchronized (_lookupCache) {
_lookupCache.put(w.name, d);
}
}
synchronized (w) {
w.notifyAll();
}
}
}
}
/**
* Called by the message handler
* on reception of HostReplyMessage
* @since 0.9.10
*/
void destLookupFailed(long nonce) {
for (LookupWaiter w : _pendingLookups) {
if (nonce == w.nonce) {
synchronized (w) { synchronized (w) {
w.notifyAll(); w.notifyAll();
} }
@ -1003,13 +1089,31 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* @since 0.8.3 * @since 0.8.3
*/ */
private static class LookupWaiter { private static class LookupWaiter {
/** the request */ /** the request (Hash mode) */
public final Hash hash; public final Hash hash;
/** the request (String mode) */
public final String name;
/** the request (nonce mode) */
public final long nonce;
/** the reply */ /** the reply */
public volatile Destination destination; public volatile Destination destination;
public LookupWaiter(Hash h) { public LookupWaiter(Hash h) {
this(h, -1);
}
/** @since 0.9.10 */
public LookupWaiter(Hash h, long nonce) {
this.hash = h; this.hash = h;
this.name = null;
this.nonce = nonce;
}
/** @since 0.9.10 */
public LookupWaiter(String name, long nonce) {
this.hash = null;
this.name = name;
this.nonce = nonce;
} }
} }
@ -1037,12 +1141,100 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (rv != null) if (rv != null)
return rv; return rv;
} }
if (isClosed()) if (isClosed()) {
if (_log.shouldLog(Log.INFO))
_log.info("Session closed, cannot lookup " + h);
return null; return null;
LookupWaiter waiter = new LookupWaiter(h); }
LookupWaiter waiter;
long nonce;
if (_routerSupportsHostLookup) {
nonce = _lookupID.incrementAndGet() & 0x7fffffff;
waiter = new LookupWaiter(h, nonce);
} else {
nonce = 0; // won't be used
waiter = new LookupWaiter(h);
}
_pendingLookups.offer(waiter); _pendingLookups.offer(waiter);
try { try {
sendMessage(new DestLookupMessage(h)); if (_routerSupportsHostLookup) {
if (_log.shouldLog(Log.INFO))
_log.info("Sending HostLookup for " + h);
sendMessage(new HostLookupMessage(h, nonce, maxWait));
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Sending DestLookup for " + h);
sendMessage(new DestLookupMessage(h));
}
try {
synchronized (waiter) {
waiter.wait(maxWait);
}
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
}
} finally {
_pendingLookups.remove(waiter);
}
return waiter.destination;
}
/**
* Ask the router to lookup a Destination by host name.
* Blocking. Waits a max of 10 seconds by default.
*
* This only makes sense for a b32 hostname, OR outside router context.
* Inside router context, just query the naming service.
* Outside router context, this does NOT query the context naming service.
* Do that first if you expect a local addressbook.
*
* This will log a warning for non-b32 in router context.
*
* See interface for suggested implementation.
*
* Requires router side to be 0.9.10 or higher. If the router is older,
* this will return null immediately.
*
* @since 0.9.10
*/
public Destination lookupDest(String name) throws I2PSessionException {
return lookupDest(name, 10*1000);
}
/**
* Ask the router to lookup a Destination by host name.
* Blocking. See above for details.
* @param maxWait ms
* @since 0.9.10
* @return null on failure
*/
public Destination lookupDest(String name, long maxWait) throws I2PSessionException {
synchronized (_lookupCache) {
Destination rv = _lookupCache.get(name);
if (rv != null)
return rv;
}
if (isClosed()) {
if (_log.shouldLog(Log.INFO))
_log.info("Session closed, cannot lookup " + name);
return null;
}
if (!_routerSupportsHostLookup) {
// do them a favor and convert to Hash lookup
if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p"))
return lookupDest(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))), maxWait);
// else unsupported
if (_log.shouldLog(Log.WARN))
_log.warn("Router does not support HostLookup for " + name);
return null;
}
int nonce = _lookupID.incrementAndGet() & 0x7fffffff;
LookupWaiter waiter = new LookupWaiter(name, nonce);
_pendingLookups.offer(waiter);
try {
if (_log.shouldLog(Log.INFO))
_log.info("Sending HostLookup for " + name);
sendMessage(new HostLookupMessage(name, nonce, maxWait));
try { try {
synchronized (waiter) { synchronized (waiter) {
waiter.wait(maxWait); waiter.wait(maxWait);

View File

@ -14,13 +14,20 @@ import java.net.UnknownHostException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Properties; import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.BandwidthLimitsMessage;
import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.DestReplyMessage;
import net.i2p.data.i2cp.DisconnectMessage;
import net.i2p.data.i2cp.GetDateMessage;
import net.i2p.data.i2cp.HostReplyMessage;
import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.SetDateMessage;
import net.i2p.internal.InternalClientManager; import net.i2p.internal.InternalClientManager;
import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.internal.QueuedI2CPMessageReader;
import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
/** /**
* Create a new session for doing naming and bandwidth queries only. Do not create a Destination. * Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
@ -68,6 +75,7 @@ class I2PSimpleSession extends I2PSessionImpl2 {
// the following may throw an I2PSessionException // the following may throw an I2PSessionException
_queue = mgr.connect(); _queue = mgr.connect();
_reader = new QueuedI2CPMessageReader(_queue, this); _reader = new QueuedI2CPMessageReader(_queue, this);
_reader.startReading();
} else { } else {
if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) { if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) {
try { try {
@ -85,14 +93,47 @@ class I2PSimpleSession extends I2PSessionImpl2 {
out.write(I2PClient.PROTOCOL_BYTE); out.write(I2PClient.PROTOCOL_BYTE);
out.flush(); out.flush();
_writer = new ClientWriterRunner(out, this); _writer = new ClientWriterRunner(out, this);
_writer.startWriting();
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE); InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
_reader = new I2CPMessageReader(in, this); _reader = new I2CPMessageReader(in, this);
_reader.startReading();
} }
} }
// must be out of synch block for writer to get unblocked
if (!_context.isRouterContext()) {
Properties opts = getOptions();
// Send auth message if required
// Auth was not enforced on a simple session until 0.9.10
// We will get disconnected for router version < 0.9.10 since it doesn't
// support the AuthMessage
if ((!opts.containsKey(PROP_USER)) && (!opts.containsKey(PROP_PW))) {
// auto-add auth if not set in the options
String configUser = _context.getProperty(PROP_USER);
String configPW = _context.getProperty(PROP_PW);
if (configUser != null && configPW != null) {
opts.setProperty(PROP_USER, configUser);
opts.setProperty(PROP_PW, configPW);
}
}
if (opts.containsKey(PROP_USER) && opts.containsKey(PROP_PW)) {
Properties auth = new OrderedProperties();
auth.setProperty(PROP_USER, opts.getProperty(PROP_USER));
auth.setProperty(PROP_PW, opts.getProperty(PROP_PW));
sendMessage(new GetDateMessage(CoreVersion.VERSION, auth));
} else {
// we must now send a GetDate even in SimpleSession, or we won't know
// what version we are talking with and cannot use HostLookup
sendMessage(new GetDateMessage(CoreVersion.VERSION));
}
waitForDate();
}
// we do not receive payload messages, so we do not need an AvailabilityNotifier // we do not receive payload messages, so we do not need an AvailabilityNotifier
// ... or an Idle timer, or a VerifyUsage // ... or an Idle timer, or a VerifyUsage
_reader.startReading();
success = true; success = true;
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + " simple session connected");
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
} catch (UnknownHostException uhe) { } catch (UnknownHostException uhe) {
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe); throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe);
} catch (IOException ioe) { } catch (IOException ioe) {
@ -115,9 +156,15 @@ class I2PSimpleSession extends I2PSessionImpl2 {
private static class SimpleMessageHandlerMap extends I2PClientMessageHandlerMap { private static class SimpleMessageHandlerMap extends I2PClientMessageHandlerMap {
public SimpleMessageHandlerMap(I2PAppContext context) { public SimpleMessageHandlerMap(I2PAppContext context) {
int highest = Math.max(DestReplyMessage.MESSAGE_TYPE, BandwidthLimitsMessage.MESSAGE_TYPE); int highest = Math.max(DestReplyMessage.MESSAGE_TYPE, BandwidthLimitsMessage.MESSAGE_TYPE);
highest = Math.max(highest, DisconnectMessage.MESSAGE_TYPE);
highest = Math.max(highest, HostReplyMessage.MESSAGE_TYPE);
highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE);
_handlers = new I2CPMessageHandler[highest+1]; _handlers = new I2CPMessageHandler[highest+1];
_handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
_handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
_handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context);
_handlers[HostReplyMessage.MESSAGE_TYPE] = new HostReplyMessageHandler(context);
_handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context);
} }
} }
} }

View File

@ -34,6 +34,9 @@ import net.i2p.data.Hash;
class LookupDest { class LookupDest {
private static final long DEFAULT_TIMEOUT = 15*1000; private static final long DEFAULT_TIMEOUT = 15*1000;
private static final String PROP_ENABLE_SSL = "i2cp.SSL";
private static final String PROP_USER = "i2cp.username";
private static final String PROP_PW = "i2cp.password";
protected LookupDest(I2PAppContext context) {} protected LookupDest(I2PAppContext context) {}
@ -61,12 +64,23 @@ class LookupDest {
Destination rv = null; Destination rv = null;
I2PClient client = new I2PSimpleClient(); I2PClient client = new I2PSimpleClient();
Properties opts = new Properties(); Properties opts = new Properties();
String s = ctx.getProperty(I2PClient.PROP_TCP_HOST); if (!ctx.isRouterContext()) {
if (s != null) String s = ctx.getProperty(I2PClient.PROP_TCP_HOST);
opts.put(I2PClient.PROP_TCP_HOST, s); if (s != null)
s = ctx.getProperty(I2PClient.PROP_TCP_PORT); opts.put(I2PClient.PROP_TCP_HOST, s);
if (s != null) s = ctx.getProperty(I2PClient.PROP_TCP_PORT);
opts.put(I2PClient.PROP_TCP_PORT, s); if (s != null)
opts.put(I2PClient.PROP_TCP_PORT, s);
s = ctx.getProperty(PROP_ENABLE_SSL);
if (s != null)
opts.put(PROP_ENABLE_SSL, s);
s = ctx.getProperty(PROP_USER);
if (s != null)
opts.put(PROP_USER, s);
s = ctx.getProperty(PROP_PW);
if (s != null)
opts.put(PROP_PW, s);
}
I2PSession session = null; I2PSession session = null;
try { try {
session = client.createSession(null, opts); session = client.createSession(null, opts);

View File

@ -12,19 +12,24 @@ package net.i2p.data.i2cp;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.util.OrderedProperties;
/** /**
* Request the other side to send us what they think the current time is/ * Request the other side to send us what they think the current time is.
* Only supported from client to router. * Only supported from client to router.
* *
* Since 0.8.7, optionally include a version string. * Since 0.8.7, optionally include a version string.
* Since 0.9.10, optionally include options.
*/ */
public class GetDateMessage extends I2CPMessageImpl { public class GetDateMessage extends I2CPMessageImpl {
public final static int MESSAGE_TYPE = 32; public final static int MESSAGE_TYPE = 32;
private String _version; private String _version;
private Properties _options;
public GetDateMessage() { public GetDateMessage() {
super(); super();
@ -39,6 +44,21 @@ public class GetDateMessage extends I2CPMessageImpl {
_version = version; _version = version;
} }
/**
* @param version the client's version String to be sent to the router; may be null;
* must be non-null if options is non-null and non-empty.
* @param options Client options to be sent to the router; primarily for authentication; may be null;
* keys and values 255 bytes (not chars) max each
* @since 0.9.10
*/
public GetDateMessage(String version, Properties options) {
super();
if (version == null && options != null && !options.isEmpty())
throw new IllegalArgumentException();
_version = version;
_options = options;
}
/** /**
* @return may be null * @return may be null
* @since 0.8.7 * @since 0.8.7
@ -47,11 +67,24 @@ public class GetDateMessage extends I2CPMessageImpl {
return _version; return _version;
} }
/**
* Retrieve any configuration options for the connection.
* Primarily for authentication.
*
* @return may be null
* @since 0.9.10
*/
public Properties getOptions() {
return _options;
}
@Override @Override
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
if (size > 0) { if (size > 0) {
try { try {
_version = DataHelper.readString(in); _version = DataHelper.readString(in);
if (size > 1 + _version.length()) // assume ascii
_options = DataHelper.readProperties(in);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
throw new I2CPMessageException("Bad version string", dfe); throw new I2CPMessageException("Bad version string", dfe);
} }
@ -62,9 +95,11 @@ public class GetDateMessage extends I2CPMessageImpl {
protected byte[] doWriteMessage() throws I2CPMessageException, IOException { protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_version == null) if (_version == null)
return new byte[0]; return new byte[0];
ByteArrayOutputStream os = new ByteArrayOutputStream(16); ByteArrayOutputStream os = new ByteArrayOutputStream(_options != null ? 128 : 16);
try { try {
DataHelper.writeString(os, _version); DataHelper.writeString(os, _version);
if (_options != null && !_options.isEmpty())
DataHelper.writeProperties(os, _options, true); // UTF-8
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe); throw new I2CPMessageException("Error writing out the message data", dfe);
} }
@ -80,6 +115,16 @@ public class GetDateMessage extends I2CPMessageImpl {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append("[GetDateMessage]"); buf.append("[GetDateMessage]");
buf.append("\n\tVersion: ").append(_version); buf.append("\n\tVersion: ").append(_version);
if (_options != null && !_options.isEmpty()) {
buf.append("\n\tOptions: #: ").append(_options.size());
Properties sorted = new OrderedProperties();
sorted.putAll(_options);
for (Map.Entry<Object, Object> e : sorted.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
}
return buf.toString(); return buf.toString();
} }
} }

View File

@ -0,0 +1,168 @@
package net.i2p.data.i2cp;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
/**
* Request the router look up the dest for a hash
* or a host. Replaces DestLookupMessage.
*
* @since 0.9.10; do not send to routers older than 0.9.10.
*/
public class HostLookupMessage extends I2CPMessageImpl {
public final static int MESSAGE_TYPE = 38;
private long _reqID;
private long _timeout;
private int _lookupType;
private Hash _hash;
private String _host;
public static final int LOOKUP_HASH = 0;
public static final int LOOKUP_HOST = 1;
private static final long MAX_INT = (1L << 32) - 1;
public HostLookupMessage() {}
/**
* @param reqID 0 to 2**32 - 1
* @param timeout ms 1 to 2**32 - 1
*/
public HostLookupMessage(Hash h, long reqID, long timeout) {
if (reqID < 0 || reqID > MAX_INT)
throw new IllegalArgumentException();
if (timeout <= 0 || timeout > MAX_INT)
throw new IllegalArgumentException();
_hash = h;
_reqID = reqID;
_timeout = timeout;
_lookupType = LOOKUP_HASH;
}
/**
* @param reqID 0 to 2**32 - 1
* @param timeout ms 1 to 2**32 - 1
*/
public HostLookupMessage(String host, long reqID, long timeout) {
if (reqID < 0 || reqID > MAX_INT)
throw new IllegalArgumentException();
if (timeout <= 0 || timeout > MAX_INT)
throw new IllegalArgumentException();
_host = host;
_reqID = reqID;
_timeout = timeout;
_lookupType = LOOKUP_HOST;
}
/**
* @return 0 to 2**32 - 1
*/
public long getReqID() {
return _reqID;
}
/**
* @return ms 1 to 2**32 - 1
*/
public long getTimeout() {
return _timeout;
}
/**
* @return 0 (hash) or 1 (host)
*/
public int getLookupType() {
return _lookupType;
}
/**
* @return only valid if lookup type == 0
*/
public Hash getHash() {
return _hash;
}
/**
* @return only valid if lookup type == 1
*/
public String getHostname() {
return _host;
}
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_reqID = DataHelper.readLong(in, 4);
_timeout = DataHelper.readLong(in, 4);
_lookupType = (int) DataHelper.readLong(in, 1);
if (_lookupType == LOOKUP_HASH) {
_hash = Hash.create(in);
} else if (_lookupType == LOOKUP_HOST) {
_host = DataHelper.readString(in);
if (_host.length() == 0)
throw new I2CPMessageException("bad host");
} else {
throw new I2CPMessageException("bad type");
}
} catch (DataFormatException dfe) {
throw new I2CPMessageException("bad data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
int len;
if (_lookupType == LOOKUP_HASH) {
if (_hash == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
len = 9 + Hash.HASH_LENGTH;
} else if (_lookupType == LOOKUP_HOST) {
if (_host == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
len = 10 + _host.length();
} else {
throw new I2CPMessageException("bad type");
}
ByteArrayOutputStream os = new ByteArrayOutputStream(len);
try {
DataHelper.writeLong(os, 4, _reqID);
DataHelper.writeLong(os, 4, _timeout);
DataHelper.writeLong(os, 1, _lookupType);
if (_lookupType == LOOKUP_HASH) {
_hash.writeBytes(os);
} else {
DataHelper.writeString(os, _host);
}
} catch (DataFormatException dfe) {
throw new I2CPMessageException("bad data", dfe);
}
return os.toByteArray();
}
public int getType() {
return MESSAGE_TYPE;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[HostLookupMessage: ");
buf.append("\n\tReqID: ").append(_reqID);
buf.append("\n\tTimeout: ").append(_timeout);
if (_lookupType == LOOKUP_HASH)
buf.append("\n\tHash: ").append(_hash);
else if (_lookupType == LOOKUP_HOST)
buf.append("\n\tHost: ").append(_host);
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,133 @@
package net.i2p.data.i2cp;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
/**
* Response to HostLookupMessage. Replaces DestReplyMessage.
*
* @since 0.9.10
*/
public class HostReplyMessage extends I2CPMessageImpl {
public final static int MESSAGE_TYPE = 39;
private Destination _dest;
private long _reqID;
private int _code;
public static final int RESULT_SUCCESS = 0;
/** generic fail, other codes TBD */
public static final int RESULT_FAILURE = 1;
private static final long MAX_INT = (1L << 32) - 1;
public HostReplyMessage() {}
/**
* A message with RESULT_SUCCESS and a non-null Destination.
*
* @param d non-null
* @param reqID 0 to 2**32 - 1
*/
public HostReplyMessage(Destination d, long reqID) {
if (d == null)
throw new IllegalArgumentException();
if (reqID < 0 || reqID > MAX_INT)
throw new IllegalArgumentException();
_dest = d;
_reqID = reqID;
}
/**
* A message with a failure code and no Destination.
*
* @param failureCode 1-255
* @param reqID from the HostLookup 0 to 2**32 - 1
*/
public HostReplyMessage(int failureCode, long reqID) {
if (failureCode <= 0 || failureCode > 255)
throw new IllegalArgumentException();
if (reqID < 0 || reqID > MAX_INT)
throw new IllegalArgumentException();
_code = failureCode;
_reqID = reqID;
}
/**
* @return 0 to 2**32 - 1
*/
public long getReqID() {
return _reqID;
}
/**
* @return 0 on success, 1-255 on failure
*/
public int getResultCode() {
return _code;
}
/**
* @return non-null only if result code is zero
*/
public Destination getDestination() {
return _dest;
}
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_reqID = DataHelper.readLong(in, 4);
_code = (int) DataHelper.readLong(in, 1);
if (_code == RESULT_SUCCESS)
_dest = Destination.create(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("bad data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
int len = 5;
if (_code == RESULT_SUCCESS) {
if (_dest == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
len += _dest.size();
}
ByteArrayOutputStream os = new ByteArrayOutputStream(len);
try {
DataHelper.writeLong(os, 4, _reqID);
DataHelper.writeLong(os, 1, _code);
if (_code == RESULT_SUCCESS)
_dest.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("bad data", dfe);
}
return os.toByteArray();
}
public int getType() {
return MESSAGE_TYPE;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[HostReplyMessage: ");
buf.append("\n\tReqID: ").append(_reqID);
buf.append("\n\tCode: ").append(_code);
if (_code == RESULT_SUCCESS)
buf.append("\n\tDestination: ").append(_dest);
buf.append("]");
return buf.toString();
}
}

View File

@ -31,7 +31,7 @@ public class I2CPMessageHandler {
* message - if it is an unknown type or has improper formatting, etc. * message - if it is an unknown type or has improper formatting, etc.
*/ */
public static I2CPMessage readMessage(InputStream in) throws IOException, I2CPMessageException { public static I2CPMessage readMessage(InputStream in) throws IOException, I2CPMessageException {
int length = -1; int length;
try { try {
length = (int) DataHelper.readLong(in, 4); length = (int) DataHelper.readLong(in, 4);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
@ -41,6 +41,10 @@ public class I2CPMessageHandler {
if (length < 0) throw new I2CPMessageException("Invalid message length specified"); if (length < 0) throw new I2CPMessageException("Invalid message length specified");
int type = (int) DataHelper.readLong(in, 1); int type = (int) DataHelper.readLong(in, 1);
I2CPMessage msg = createMessage(type); I2CPMessage msg = createMessage(type);
// Note that the readMessage() calls don't, in general, read and discard
// extra data, so we can't add new fields to the end of messages
// in a compatible way. And the readers could read beyond the length too.
// To fix this we'd have to read into a BAOS/BAIS or use a filter input stream
msg.readMessage(in, length, type); msg.readMessage(in, length, type);
return msg; return msg;
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
@ -52,7 +56,7 @@ public class I2CPMessageHandler {
* Yes, this is fairly ugly, but its the only place it ever happens. * Yes, this is fairly ugly, but its the only place it ever happens.
* *
*/ */
private static I2CPMessage createMessage(int type) throws IOException, private static I2CPMessage createMessage(int type) throws
I2CPMessageException { I2CPMessageException {
switch (type) { switch (type) {
case CreateLeaseSetMessage.MESSAGE_TYPE: case CreateLeaseSetMessage.MESSAGE_TYPE:
@ -97,6 +101,10 @@ public class I2CPMessageHandler {
return new GetBandwidthLimitsMessage(); return new GetBandwidthLimitsMessage();
case BandwidthLimitsMessage.MESSAGE_TYPE: case BandwidthLimitsMessage.MESSAGE_TYPE:
return new BandwidthLimitsMessage(); return new BandwidthLimitsMessage();
case HostLookupMessage.MESSAGE_TYPE:
return new HostLookupMessage();
case HostReplyMessage.MESSAGE_TYPE:
return new HostReplyMessage();
default: default:
throw new I2CPMessageException("The type " + type + " is an unknown I2CP message"); throw new I2CPMessageException("The type " + type + " is an unknown I2CP message");
} }

View File

@ -110,7 +110,7 @@ public class I2CPMessageReader {
* reader * reader
* *
* @param reader I2CPMessageReader to notify * @param reader I2CPMessageReader to notify
* @param error Exception that was thrown * @param error Exception that was thrown, non-null
*/ */
public void readError(I2CPMessageReader reader, Exception error); public void readError(I2CPMessageReader reader, Exception error);

View File

@ -89,7 +89,8 @@ public class SessionConfig extends DataStructureImpl {
} }
/** /**
* Configure the session with the given options * Configure the session with the given options;
* keys and values 255 bytes (not chars) max each
* *
* @param options Properties for this session * @param options Properties for this session
*/ */

View File

@ -335,12 +335,14 @@ class ClientConnectionRunner {
* This is always bad. * This is always bad.
* See ClientMessageEventListener.handleCreateSession() * See ClientMessageEventListener.handleCreateSession()
* for why we don't send a SessionStatusMessage when we do this. * for why we don't send a SessionStatusMessage when we do this.
* @param reason will be truncated to 255 bytes
*/ */
void disconnectClient(String reason) { void disconnectClient(String reason) {
disconnectClient(reason, Log.ERROR); disconnectClient(reason, Log.ERROR);
} }
/** /**
* @param reason will be truncated to 255 bytes
* @param logLevel e.g. Log.WARN * @param logLevel e.g. Log.WARN
* @since 0.8.2 * @since 0.8.2
*/ */
@ -351,6 +353,8 @@ class ClientConnectionRunner {
+ " config: " + " config: "
+ _config); + _config);
DisconnectMessage msg = new DisconnectMessage(); DisconnectMessage msg = new DisconnectMessage();
if (reason.length() > 255)
reason = reason.substring(0, 255);
msg.setReason(reason); msg.setReason(reason);
try { try {
doSend(msg); doSend(msg);

View File

@ -20,6 +20,7 @@ import net.i2p.data.i2cp.DestLookupMessage;
import net.i2p.data.i2cp.DestroySessionMessage; import net.i2p.data.i2cp.DestroySessionMessage;
import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
import net.i2p.data.i2cp.GetDateMessage; import net.i2p.data.i2cp.GetDateMessage;
import net.i2p.data.i2cp.HostLookupMessage;
import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.I2CPMessageReader;
@ -50,8 +51,11 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
protected final RouterContext _context; protected final RouterContext _context;
protected final ClientConnectionRunner _runner; protected final ClientConnectionRunner _runner;
private final boolean _enforceAuth; private final boolean _enforceAuth;
private volatile boolean _authorized;
private static final String PROP_AUTH = "i2cp.auth"; 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 * @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); _log = _context.logManager().getLog(ClientMessageEventListener.class);
_runner = runner; _runner = runner;
_enforceAuth = enforceAuth; _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 }); _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 (_runner.isDead()) return;
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Message received: \n" + message); _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()) { switch (message.getType()) {
case GetDateMessage.MESSAGE_TYPE: case GetDateMessage.MESSAGE_TYPE:
handleGetDate((GetDateMessage)message); handleGetDate((GetDateMessage)message);
@ -103,6 +123,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
case DestLookupMessage.MESSAGE_TYPE: case DestLookupMessage.MESSAGE_TYPE:
handleDestLookup((DestLookupMessage)message); handleDestLookup((DestLookupMessage)message);
break; break;
case HostLookupMessage.MESSAGE_TYPE:
handleHostLookup((HostLookupMessage)message);
break;
case ReconfigureSessionMessage.MESSAGE_TYPE: case ReconfigureSessionMessage.MESSAGE_TYPE:
handleReconfigureSession((ReconfigureSessionMessage)message); handleReconfigureSession((ReconfigureSessionMessage)message);
break; break;
@ -124,6 +147,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
_log.error("Error occurred", error); _log.error("Error occurred", error);
// Is this is a little drastic for an unknown message type? // 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(); _runner.stopRunning();
} }
@ -137,6 +162,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
String clientVersion = message.getVersion(); String clientVersion = message.getVersion();
if (clientVersion != null) if (clientVersion != null)
_runner.setClientVersion(clientVersion); _runner.setClientVersion(clientVersion);
Properties props = message.getOptions();
if (!checkAuth(props))
return;
try { try {
// only send version if the client can handle it (0.8.7 or greater) // only send version if the client can handle it (0.8.7 or greater)
_runner.doSend(new SetDateMessage(clientVersion != null ? CoreVersion.VERSION : null)); _runner.doSend(new SetDateMessage(clientVersion != null ? CoreVersion.VERSION : null));
@ -174,24 +202,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
} }
// Auth, since 0.8.2 // Auth, since 0.8.2
if (_enforceAuth && _context.getBooleanProperty(PROP_AUTH)) { Properties inProps = in.getOptions();
Properties props = in.getOptions(); if (!checkAuth(inProps))
String user = props.getProperty("i2cp.username"); return;
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);
}
SessionId sessionId = new SessionId(); SessionId sessionId = new SessionId();
sessionId.setSessionId(getNextSessionId()); sessionId.setSessionId(getNextSessionId());
@ -213,6 +226,44 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
startCreateSessionJob(); 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 * Override for testing
* @since 0.9.8 * @since 0.9.8
@ -315,6 +366,15 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
_context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash())); _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. * Message's Session ID ignored. This doesn't support removing previously set options.
* Nor do we bother with message.getSessionConfig().verifySignature() ... should we? * Nor do we bother with message.getSessionConfig().verifySignature() ... should we?

View File

@ -4,32 +4,83 @@
*/ */
package net.i2p.router.client; package net.i2p.router.client;
import java.util.Locale;
import net.i2p.data.Base32;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.LeaseSet; import net.i2p.data.LeaseSet;
import net.i2p.data.i2cp.DestReplyMessage; 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.data.i2cp.I2CPMessageException;
import net.i2p.router.JobImpl; import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext; 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 { class LookupDestJob extends JobImpl {
private final ClientConnectionRunner _runner; private final ClientConnectionRunner _runner;
private final long _reqID;
private final long _timeout;
private final Hash _hash; private final Hash _hash;
private final String _name;
private static final long DEFAULT_TIMEOUT = 15*1000;
public LookupDestJob(RouterContext context, ClientConnectionRunner runner, Hash h) { 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); super(context);
if ((h == null && name == null) ||
(h != null && name != null) ||
(reqID < 0 && name != null))
throw new IllegalArgumentException();
_runner = runner; _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; _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() { public void runJob() {
DoneJob done = new DoneJob(getContext()); if (_name != null) {
// TODO add support for specifying the timeout in the lookup message // inline, ignore timeout
getContext().netDb().lookupLeaseSet(_hash, done, done, 15*1000); 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 { private class DoneJob extends JobImpl {
@ -42,12 +93,16 @@ class LookupDestJob extends JobImpl {
if (ls != null) if (ls != null)
returnDest(ls.getDestination()); returnDest(ls.getDestination());
else else
returnHash(_hash); returnFail();
} }
} }
private void returnDest(Destination d) { 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 { try {
_runner.doSend(msg); _runner.doSend(msg);
} catch (I2CPMessageException ime) {} } catch (I2CPMessageException ime) {}
@ -57,8 +112,12 @@ class LookupDestJob extends JobImpl {
* Return the failed hash so the client can correlate replies with requests * Return the failed hash so the client can correlate replies with requests
* @since 0.8.3 * @since 0.8.3
*/ */
private void returnHash(Hash h) { private void returnFail() {
DestReplyMessage msg = new DestReplyMessage(h); I2CPMessage msg;
if (_reqID >= 0)
msg = new HostReplyMessage(HostReplyMessage.RESULT_FAILURE, _reqID);
else
msg = new DestReplyMessage(_hash);
try { try {
_runner.doSend(msg); _runner.doSend(msg);
} catch (I2CPMessageException ime) {} } catch (I2CPMessageException ime) {}