forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.i2cp' (head d4ac8162a4ba299ac912640f19076c3c90afdc67)
to branch 'i2p.i2p' (head adc5102c93383e01c74b87f04449dc9c307f6e75)
This commit is contained in:
@ -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 [-acx] [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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1441,20 +1508,19 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
|||||||
*/
|
*/
|
||||||
private void runPing(String allargs, Logging l) {
|
private void runPing(String allargs, Logging l) {
|
||||||
if (allargs.length() != 0) {
|
if (allargs.length() != 0) {
|
||||||
I2PTunnelTask task;
|
_clientOptions.setProperty(I2Ping.PROP_COMMAND, allargs);
|
||||||
// pings always use the main destination
|
I2PTunnelTask task = new I2Ping(l, ownDest, this, this);
|
||||||
task = new I2Ping(allargs, l, false, this, this);
|
|
||||||
addtask(task);
|
addtask(task);
|
||||||
notifyEvent("pingTaskId", Integer.valueOf(task.getId()));
|
notifyEvent("pingTaskId", Integer.valueOf(task.getId()));
|
||||||
} else {
|
} else {
|
||||||
l.log("ping <opts> <dest>");
|
l.log("ping <opts> <b64dest|host>");
|
||||||
l.log("ping <opts> -h (pings all hosts in hosts.txt)");
|
l.log("ping <opts> -h (pings all hosts in hosts.txt)");
|
||||||
l.log("ping <opts> -l <destlistfile> (pings a list of hosts in a file)");
|
l.log("ping <opts> -l <destlistfile> (pings a list of hosts in a file)");
|
||||||
l.log(" Options:\n" +
|
l.log(" Options:\n" +
|
||||||
" -c (require 5 consecutive pings to report success)\n" +
|
" -c (require 5 consecutive pings to report success)\n" +
|
||||||
" -m maxSimultaneousPings (default 10)\n" +
|
" -m maxSimultaneousPings (default 10)\n" +
|
||||||
" -n numberOfPings (default 3)\n" +
|
" -n numberOfPings (default 3)\n" +
|
||||||
" -t timeout (ms, default 5000)\n");
|
" -t timeout (ms, default 30000)\n");
|
||||||
l.log(" Tests communication with peers.\n");
|
l.log(" Tests communication with peers.\n");
|
||||||
notifyEvent("pingTaskId", Integer.valueOf(-1));
|
notifyEvent("pingTaskId", Integer.valueOf(-1));
|
||||||
}
|
}
|
||||||
@ -1599,6 +1665,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 +1721,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() || name.length() >= 516)
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,15 +51,18 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
|||||||
protected final List<I2PSocket> mySockets = new ArrayList<I2PSocket>();
|
protected final List<I2PSocket> mySockets = new ArrayList<I2PSocket>();
|
||||||
protected boolean _ownDest;
|
protected boolean _ownDest;
|
||||||
|
|
||||||
protected Destination dest = null;
|
protected Destination dest;
|
||||||
private int localPort;
|
private int localPort;
|
||||||
|
|
||||||
private boolean listenerReady = false;
|
/**
|
||||||
|
* Protected for I2Ping since 0.9.10. Not for use outside package.
|
||||||
|
*/
|
||||||
|
protected boolean listenerReady;
|
||||||
|
|
||||||
protected ServerSocket ss;
|
protected ServerSocket ss;
|
||||||
|
|
||||||
private final Object startLock = new Object();
|
private final Object startLock = new Object();
|
||||||
private boolean startRunning = false;
|
private boolean startRunning;
|
||||||
|
|
||||||
// private Object closeLock = new Object();
|
// private Object closeLock = new Object();
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
|||||||
private String privKeyFile;
|
private String privKeyFile;
|
||||||
|
|
||||||
// true if we are chained from a server.
|
// true if we are chained from a server.
|
||||||
private boolean chained = false;
|
private boolean chained;
|
||||||
|
|
||||||
/** how long to wait before dropping an idle thread */
|
/** how long to wait before dropping an idle thread */
|
||||||
private static final long HANDLER_KEEPALIVE_MS = 2*60*1000;
|
private static final long HANDLER_KEEPALIVE_MS = 2*60*1000;
|
||||||
@ -582,7 +585,11 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
|||||||
return i2ps;
|
return i2ps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void run() {
|
/**
|
||||||
|
* Non-final since 0.9.10.
|
||||||
|
* Any overrides must set listenerReady = true.
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
try {
|
try {
|
||||||
InetAddress addr = getListenHost(l);
|
InetAddress addr = getListenHost(l);
|
||||||
if (addr == null) {
|
if (addr == null) {
|
||||||
|
@ -6,69 +6,70 @@ package net.i2p.i2ptunnel;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
|
import net.i2p.client.I2PSession;
|
||||||
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.client.streaming.I2PSocketManager;
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.EventDispatcher;
|
import net.i2p.util.EventDispatcher;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
public class I2Ping extends I2PTunnelTask implements Runnable {
|
/**
|
||||||
private final static Log _log = new Log(I2Ping.class);
|
* Warning - not necessarily a stable API.
|
||||||
|
* Used by I2PTunnel CLI only. Consider this sample code.
|
||||||
|
* Not for use outside this package.
|
||||||
|
*/
|
||||||
|
public class I2Ping extends I2PTunnelClientBase {
|
||||||
|
|
||||||
private int PING_COUNT = 3;
|
public static final String PROP_COMMAND = "command";
|
||||||
|
|
||||||
|
private static final int PING_COUNT = 3;
|
||||||
private static final int CPING_COUNT = 5;
|
private static final int CPING_COUNT = 5;
|
||||||
private static final int PING_TIMEOUT = 5000;
|
private static final int PING_TIMEOUT = 30*1000;
|
||||||
|
|
||||||
private static final long PING_DISTANCE = 1000;
|
private static final long PING_DISTANCE = 1000;
|
||||||
|
|
||||||
private int MAX_SIMUL_PINGS = 10; // not really final...
|
private int MAX_SIMUL_PINGS = 10; // not really final...
|
||||||
|
|
||||||
private boolean countPing = false;
|
|
||||||
private boolean reportTimes = true;
|
|
||||||
|
|
||||||
private I2PSocketManager sockMgr;
|
private volatile boolean finished;
|
||||||
private Logging l;
|
|
||||||
private boolean finished = false;
|
|
||||||
private String command;
|
|
||||||
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();
|
/**
|
||||||
|
* tunnel.getOptions must contain "command".
|
||||||
//public I2Ping(String cmd, Logging l,
|
* @throws IllegalArgumentException if it doesn't
|
||||||
// boolean ownDest) {
|
*/
|
||||||
// I2Ping(cmd, l, (EventDispatcher)null);
|
public I2Ping(Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||||
//}
|
super(-1, ownDest, l, notifyThis, "I2Ping", tunnel);
|
||||||
|
if (!tunnel.getClientOptions().containsKey(PROP_COMMAND)) {
|
||||||
public I2Ping(String cmd, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
// todo clean up
|
||||||
super("I2Ping [" + cmd + "]", notifyThis, tunnel);
|
throw new IllegalArgumentException("Options does not contain " + PROP_COMMAND);
|
||||||
this.l = l;
|
|
||||||
command = cmd;
|
|
||||||
synchronized (slock) {
|
|
||||||
if (ownDest) {
|
|
||||||
sockMgr = I2PTunnelClient.buildSocketManager(tunnel);
|
|
||||||
} else {
|
|
||||||
sockMgr = I2PTunnelClient.getSocketManager(tunnel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Thread t = new I2PAppThread(this);
|
|
||||||
t.setName("Client");
|
|
||||||
t.start();
|
|
||||||
open = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides super. No client ServerSocket is created.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// Notify constructor that port is ready
|
||||||
|
synchronized (this) {
|
||||||
|
listenerReady = true;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
l.log("*** I2Ping results:");
|
l.log("*** I2Ping results:");
|
||||||
try {
|
try {
|
||||||
runCommand(command);
|
runCommand(getTunnel().getClientOptions().getProperty(PROP_COMMAND));
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
l.log("*** Interrupted");
|
l.log("*** Interrupted");
|
||||||
_log.error("Pinger interrupted", ex);
|
_log.error("Pinger interrupted", ex);
|
||||||
@ -76,13 +77,15 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
_log.error("Pinger exception", ex);
|
_log.error("Pinger exception", ex);
|
||||||
}
|
}
|
||||||
l.log("*** Finished.");
|
l.log("*** Finished.");
|
||||||
synchronized (lock) {
|
finished = true;
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
close(false);
|
close(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runCommand(String cmd) throws InterruptedException, IOException {
|
public void runCommand(String cmd) throws InterruptedException, IOException {
|
||||||
|
long timeout = PING_TIMEOUT;
|
||||||
|
int count = PING_COUNT;
|
||||||
|
boolean countPing = false;
|
||||||
|
boolean reportTimes = true;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (cmd.startsWith("-t ")) { // timeout
|
if (cmd.startsWith("-t ")) { // timeout
|
||||||
cmd = cmd.substring(3);
|
cmd = cmd.substring(3);
|
||||||
@ -92,6 +95,9 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
timeout = Long.parseLong(cmd.substring(0, pos));
|
timeout = Long.parseLong(cmd.substring(0, pos));
|
||||||
|
// convenience, convert msec to sec
|
||||||
|
if (timeout < 100)
|
||||||
|
timeout *= 1000;
|
||||||
cmd = cmd.substring(pos + 1);
|
cmd = cmd.substring(pos + 1);
|
||||||
}
|
}
|
||||||
} else if (cmd.startsWith("-m ")) { // max simultaneous pings
|
} else if (cmd.startsWith("-m ")) { // max simultaneous pings
|
||||||
@ -111,11 +117,12 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
l.log("Syntax error");
|
l.log("Syntax error");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
PING_COUNT = Integer.parseInt(cmd.substring(0, pos));
|
count = Integer.parseInt(cmd.substring(0, pos));
|
||||||
cmd = cmd.substring(pos + 1);
|
cmd = cmd.substring(pos + 1);
|
||||||
}
|
}
|
||||||
} else if (cmd.startsWith("-c ")) { // "count" ping
|
} else if (cmd.startsWith("-c ")) { // "count" ping
|
||||||
countPing = true;
|
countPing = true;
|
||||||
|
count = CPING_COUNT;
|
||||||
cmd = cmd.substring(3);
|
cmd = cmd.substring(3);
|
||||||
} else if (cmd.equals("-h")) { // ping all hosts
|
} else if (cmd.equals("-h")) { // ping all hosts
|
||||||
cmd = "-l hosts.txt";
|
cmd = "-l hosts.txt";
|
||||||
@ -131,7 +138,9 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
if (line.indexOf("=") != -1) { // maybe file is hosts.txt?
|
if (line.indexOf("=") != -1) { // maybe file is hosts.txt?
|
||||||
line = line.substring(0, line.indexOf("="));
|
line = line.substring(0, line.indexOf("="));
|
||||||
}
|
}
|
||||||
pingHandlers.add(new PingHandler(line));
|
PingHandler ph = new PingHandler(line, count, timeout, countPing, reportTimes);
|
||||||
|
ph.start();
|
||||||
|
pingHandlers.add(ph);
|
||||||
if (++i > 1)
|
if (++i > 1)
|
||||||
reportTimes = false;
|
reportTimes = false;
|
||||||
}
|
}
|
||||||
@ -140,28 +149,28 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
t.join();
|
t.join();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
Thread t = new PingHandler(cmd);
|
Thread t = new PingHandler(cmd, count, timeout, countPing, reportTimes);
|
||||||
|
t.start();
|
||||||
t.join();
|
t.join();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean close(boolean forced) {
|
public boolean close(boolean forced) {
|
||||||
if (!open) return true;
|
if (!open) return true;
|
||||||
synchronized (lock) {
|
super.close(forced);
|
||||||
if (!forced && !finished) {
|
if (!forced && !finished) {
|
||||||
l.log("There are still pings running!");
|
l.log("There are still pings running!");
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
l.log("Closing pinger " + toString());
|
|
||||||
l.log("Pinger closed.");
|
|
||||||
open = false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
l.log("Closing pinger " + toString());
|
||||||
|
l.log("Pinger closed.");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean ping(Destination dest) throws I2PException {
|
private boolean ping(Destination dest, long timeout) throws I2PException {
|
||||||
try {
|
try {
|
||||||
synchronized (simulLock) {
|
synchronized (simulLock) {
|
||||||
while (simulPings >= MAX_SIMUL_PINGS) {
|
while (simulPings >= MAX_SIMUL_PINGS) {
|
||||||
@ -186,33 +195,48 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PingHandler extends I2PAppThread {
|
/**
|
||||||
private String destination;
|
* Does nothing.
|
||||||
|
* @since 0.9.10
|
||||||
|
*/
|
||||||
|
protected void clientConnectionRun(Socket s) {}
|
||||||
|
|
||||||
public PingHandler(String dest) {
|
private class PingHandler extends I2PAppThread {
|
||||||
|
private final String destination;
|
||||||
|
private final int cnt;
|
||||||
|
private final long timeout;
|
||||||
|
private final boolean countPing;
|
||||||
|
private final boolean reportTimes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As of 0.9.10, does NOT start itself.
|
||||||
|
* Caller must call start()
|
||||||
|
* @param dest b64 or b32 or host name
|
||||||
|
*/
|
||||||
|
public PingHandler(String dest, int count, long timeout, boolean countPings, boolean report) {
|
||||||
this.destination = dest;
|
this.destination = dest;
|
||||||
|
cnt = count;
|
||||||
|
this.timeout = timeout;
|
||||||
|
countPing = countPings;
|
||||||
|
reportTimes = report;
|
||||||
setName("PingHandler for " + dest);
|
setName("PingHandler for " + dest);
|
||||||
start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(destination);
|
Destination dest = lookup(destination);
|
||||||
if (dest == null) {
|
if (dest == null) {
|
||||||
synchronized (lock) { // Logger is not thread safe
|
l.log("Unresolvable: " + destination);
|
||||||
l.log("Unresolvable: " + destination + "");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int pass = 0;
|
int pass = 0;
|
||||||
int fail = 0;
|
int fail = 0;
|
||||||
long totalTime = 0;
|
long totalTime = 0;
|
||||||
int cnt = countPing ? CPING_COUNT : PING_COUNT;
|
|
||||||
StringBuilder pingResults = new StringBuilder(2 * cnt + destination.length() + 3);
|
StringBuilder pingResults = new StringBuilder(2 * cnt + destination.length() + 3);
|
||||||
for (int i = 0; i < cnt; i++) {
|
for (int i = 0; i < cnt; i++) {
|
||||||
boolean sent;
|
boolean sent;
|
||||||
sent = ping(dest);
|
sent = ping(dest, timeout);
|
||||||
if (countPing) {
|
if (countPing) {
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
pingResults.append(i).append(" ");
|
pingResults.append(i).append(" ");
|
||||||
@ -244,12 +268,35 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
|||||||
pingResults.append("and ").append(fail).append(" lost for destination: ");
|
pingResults.append("and ").append(fail).append(" lost for destination: ");
|
||||||
}
|
}
|
||||||
pingResults.append(" ").append(destination);
|
pingResults.append(" ").append(destination);
|
||||||
synchronized (lock) { // Logger is not thread safe
|
l.log(pingResults.toString());
|
||||||
l.log(pingResults.toString());
|
|
||||||
}
|
|
||||||
} catch (I2PException ex) {
|
} catch (I2PException ex) {
|
||||||
_log.error("Error pinging " + destination, ex);
|
_log.error("Error pinging " + destination, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name b64 or b32 or host name
|
||||||
|
* @since 0.9.10
|
||||||
|
*/
|
||||||
|
private Destination lookup(String name) {
|
||||||
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
|
boolean b32 = name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p");
|
||||||
|
if (ctx.isRouterContext() && !b32) {
|
||||||
|
// Local lookup.
|
||||||
|
// Even though we could do b32 outside router ctx here,
|
||||||
|
// we do it below instead so we can use the session,
|
||||||
|
// which we can't do with lookup()
|
||||||
|
Destination dest = ctx.namingService().lookup(name);
|
||||||
|
if (dest != null || ctx.isRouterContext() || name.length() >= 516)
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
I2PSession sess = sockMgr.getSession();
|
||||||
|
return sess.lookupDest(name);
|
||||||
|
} catch (I2PSessionException ise) {
|
||||||
|
_log.error("Error looking up " + name, ise);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -593,8 +593,9 @@ class ConnectionManager {
|
|||||||
Long id = Long.valueOf(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1);
|
Long id = Long.valueOf(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1);
|
||||||
PacketLocal packet = new PacketLocal(_context, peer);
|
PacketLocal packet = new PacketLocal(_context, peer);
|
||||||
packet.setSendStreamId(id.longValue());
|
packet.setSendStreamId(id.longValue());
|
||||||
packet.setFlag(Packet.FLAG_ECHO);
|
packet.setFlag(Packet.FLAG_ECHO |
|
||||||
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
Packet.FLAG_NO_ACK |
|
||||||
|
Packet.FLAG_SIGNATURE_INCLUDED);
|
||||||
packet.setOptionalFrom(_session.getMyDestination());
|
packet.setOptionalFrom(_session.getMyDestination());
|
||||||
//if ( (keyToUse != null) && (tagsToSend != null) ) {
|
//if ( (keyToUse != null) && (tagsToSend != null) ) {
|
||||||
// packet.setKeyUsed(keyToUse);
|
// packet.setKeyUsed(keyToUse);
|
||||||
|
@ -295,8 +295,15 @@ class Packet {
|
|||||||
*/
|
*/
|
||||||
public boolean isFlagSet(int flag) { return 0 != (_flags & flag); }
|
public boolean isFlagSet(int flag) { return 0 != (_flags & flag); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param flag bitmask of any flag(s)
|
||||||
|
*/
|
||||||
public void setFlag(int flag) { _flags |= flag; }
|
public void setFlag(int flag) { _flags |= flag; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param flag bitmask of any flag(s)
|
||||||
|
* @param set true to set, false to clear
|
||||||
|
*/
|
||||||
public void setFlag(int flag, boolean set) {
|
public void setFlag(int flag, boolean set) {
|
||||||
if (set)
|
if (set)
|
||||||
_flags |= flag;
|
_flags |= flag;
|
||||||
@ -304,7 +311,7 @@ class Packet {
|
|||||||
_flags &= ~flag;
|
_flags &= ~flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlags(int flags) { _flags = flags; }
|
private void setFlags(int flags) { _flags = flags; }
|
||||||
|
|
||||||
/** the signature on the packet (only included if the flag for it is set)
|
/** the signature on the packet (only included if the flag for it is set)
|
||||||
* @return signature on the packet if the flag for signatures is set
|
* @return signature on the packet if the flag for signatures is set
|
||||||
|
@ -349,8 +349,7 @@ class PacketHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PacketLocal pong = new PacketLocal(_context, packet.getOptionalFrom());
|
PacketLocal pong = new PacketLocal(_context, packet.getOptionalFrom());
|
||||||
pong.setFlag(Packet.FLAG_ECHO, true);
|
pong.setFlag(Packet.FLAG_ECHO | Packet.FLAG_NO_ACK);
|
||||||
pong.setFlag(Packet.FLAG_SIGNATURE_INCLUDED, false);
|
|
||||||
pong.setReceiveStreamId(packet.getSendStreamId());
|
pong.setReceiveStreamId(packet.getSendStreamId());
|
||||||
_manager.getPacketQueue().enqueue(pong);
|
_manager.getPacketQueue().enqueue(pong);
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,13 @@ class PacketQueue {
|
|||||||
options.setTagsToSend(INITIAL_TAGS_TO_SEND);
|
options.setTagsToSend(INITIAL_TAGS_TO_SEND);
|
||||||
options.setTagThreshold(MIN_TAG_THRESHOLD);
|
options.setTagThreshold(MIN_TAG_THRESHOLD);
|
||||||
} else if (packet.isFlagSet(FLAGS_FINAL_TAGS)) {
|
} else if (packet.isFlagSet(FLAGS_FINAL_TAGS)) {
|
||||||
options.setSendLeaseSet(false);
|
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
|
||||||
|
// Send LS for PING, not for PONG
|
||||||
|
if (packet.getSendStreamId() <= 0) // pong
|
||||||
|
options.setSendLeaseSet(false);
|
||||||
|
} else {
|
||||||
|
options.setSendLeaseSet(false);
|
||||||
|
}
|
||||||
options.setTagsToSend(FINAL_TAGS_TO_SEND);
|
options.setTagsToSend(FINAL_TAGS_TO_SEND);
|
||||||
options.setTagThreshold(FINAL_TAG_THRESHOLD);
|
options.setTagThreshold(FINAL_TAG_THRESHOLD);
|
||||||
} else {
|
} else {
|
||||||
|
@ -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.11 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.11
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
38
core/java/src/net/i2p/client/HostReplyMessageHandler.java
Normal file
38
core/java/src/net/i2p/client/HostReplyMessageHandler.java
Normal 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.11
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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.11 or higher. If the router is older,
|
||||||
|
* this will return null immediately.
|
||||||
|
*
|
||||||
|
* @since 0.9.11
|
||||||
|
*/
|
||||||
|
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.11
|
||||||
|
* @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.
|
||||||
|
@ -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.11 */
|
||||||
|
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.11, 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.11";
|
||||||
|
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.11 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.11 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.11
|
||||||
|
*/
|
||||||
|
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.11
|
||||||
|
*/
|
||||||
|
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.11 */
|
||||||
|
public LookupWaiter(Hash h, long nonce) {
|
||||||
this.hash = h;
|
this.hash = h;
|
||||||
|
this.name = null;
|
||||||
|
this.nonce = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.11 */
|
||||||
|
public LookupWaiter(String name, long nonce) {
|
||||||
|
this.hash = null;
|
||||||
|
this.name = name;
|
||||||
|
this.nonce = nonce;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1037,12 +1141,119 @@ 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);
|
||||||
|
SessionId id = _sessionId;
|
||||||
|
if (id == null)
|
||||||
|
id = new SessionId(65535);
|
||||||
|
sendMessage(new HostLookupMessage(id, 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.11 or higher. If the router is older,
|
||||||
|
* this will return null immediately.
|
||||||
|
*
|
||||||
|
* @since 0.9.11
|
||||||
|
*/
|
||||||
|
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.11
|
||||||
|
* @return null on failure
|
||||||
|
*/
|
||||||
|
public Destination lookupDest(String name, long maxWait) throws I2PSessionException {
|
||||||
|
if (name.length() == 0)
|
||||||
|
return null;
|
||||||
|
// Shortcut for b64
|
||||||
|
if (name.length() >= 516) {
|
||||||
|
try {
|
||||||
|
return new Destination(name);
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// won't fit in Mapping
|
||||||
|
if (name.length() >= 256 && !_context.isRouterContext())
|
||||||
|
return null;
|
||||||
|
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);
|
||||||
|
SessionId id = _sessionId;
|
||||||
|
if (id == null)
|
||||||
|
id = new SessionId(65535);
|
||||||
|
sendMessage(new HostLookupMessage(id, name, nonce, maxWait));
|
||||||
try {
|
try {
|
||||||
synchronized (waiter) {
|
synchronized (waiter) {
|
||||||
waiter.wait(maxWait);
|
waiter.wait(maxWait);
|
||||||
|
@ -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.11
|
||||||
|
// We will get disconnected for router version < 0.9.11 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
183
core/java/src/net/i2p/data/i2cp/HostLookupMessage.java
Normal file
183
core/java/src/net/i2p/data/i2cp/HostLookupMessage.java
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
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.11; do not send to routers older than 0.9.11.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
private SessionId _sessionId;
|
||||||
|
|
||||||
|
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(SessionId id, Hash h, long reqID, long timeout) {
|
||||||
|
if (id == null || h == null)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (reqID < 0 || reqID > MAX_INT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (timeout <= 0 || timeout > MAX_INT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
_sessionId = id;
|
||||||
|
_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(SessionId id, String host, long reqID, long timeout) {
|
||||||
|
if (id == null || host == null)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (reqID < 0 || reqID > MAX_INT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (timeout <= 0 || timeout > MAX_INT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
_sessionId = id;
|
||||||
|
_host = host;
|
||||||
|
_reqID = reqID;
|
||||||
|
_timeout = timeout;
|
||||||
|
_lookupType = LOOKUP_HOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionId getSessionId() {
|
||||||
|
return _sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 {
|
||||||
|
_sessionId = new SessionId();
|
||||||
|
_sessionId.readBytes(in);
|
||||||
|
_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 = 11 + 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 = 12 + _host.length();
|
||||||
|
} else {
|
||||||
|
throw new I2CPMessageException("bad type");
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream(len);
|
||||||
|
try {
|
||||||
|
_sessionId.writeBytes(os);
|
||||||
|
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\t").append(_sessionId);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
146
core/java/src/net/i2p/data/i2cp/HostReplyMessage.java
Normal file
146
core/java/src/net/i2p/data/i2cp/HostReplyMessage.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
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.11
|
||||||
|
*/
|
||||||
|
public class HostReplyMessage extends I2CPMessageImpl {
|
||||||
|
public final static int MESSAGE_TYPE = 39;
|
||||||
|
|
||||||
|
private Destination _dest;
|
||||||
|
private long _reqID;
|
||||||
|
private int _code;
|
||||||
|
private SessionId _sessionId;
|
||||||
|
|
||||||
|
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(SessionId id, Destination d, long reqID) {
|
||||||
|
if (id == null || d == null)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (reqID < 0 || reqID > MAX_INT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
_sessionId = id;
|
||||||
|
_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(SessionId id, int failureCode, long reqID) {
|
||||||
|
if (id == null)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (failureCode <= 0 || failureCode > 255)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (reqID < 0 || reqID > MAX_INT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
_sessionId = id;
|
||||||
|
_code = failureCode;
|
||||||
|
_reqID = reqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionId getSessionId() {
|
||||||
|
return _sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 {
|
||||||
|
_sessionId = new SessionId();
|
||||||
|
_sessionId.readBytes(in);
|
||||||
|
_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 = 7;
|
||||||
|
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 {
|
||||||
|
_sessionId.writeBytes(os);
|
||||||
|
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\t").append(_sessionId);
|
||||||
|
buf.append("\n\tReqID: ").append(_reqID);
|
||||||
|
buf.append("\n\tResult: ").append(_code);
|
||||||
|
if (_code == RESULT_SUCCESS)
|
||||||
|
buf.append("\n\tDestination: ").append(_dest);
|
||||||
|
buf.append("]");
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,12 @@ import net.i2p.data.DataHelper;
|
|||||||
*/
|
*/
|
||||||
public class I2CPMessageHandler {
|
public class I2CPMessageHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is huge. Mainly to catch a completly bogus response, possibly not an I2CP socket.
|
||||||
|
* @since 0.9.10
|
||||||
|
*/
|
||||||
|
public static final int MAX_LENGTH = 128*1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an I2CPMessage from the stream and return the fully populated object.
|
* Read an I2CPMessage from the stream and return the fully populated object.
|
||||||
*
|
*
|
||||||
@ -31,16 +37,21 @@ 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) {
|
||||||
throw new IOException("Connection closed");
|
throw new IOException("Connection closed");
|
||||||
}
|
}
|
||||||
|
if (length > MAX_LENGTH)
|
||||||
|
throw new I2CPMessageException("Invalid message length specified");
|
||||||
try {
|
try {
|
||||||
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 +63,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 +108,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");
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -29,12 +29,24 @@ public class SessionId extends DataStructureImpl {
|
|||||||
_sessionId = -1;
|
_sessionId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id 0-65535
|
||||||
|
* @since 0.9.11
|
||||||
|
*/
|
||||||
|
public SessionId(int id) {
|
||||||
|
if (id < 0 || id > 65535)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
_sessionId = id;
|
||||||
|
}
|
||||||
|
|
||||||
public int getSessionId() {
|
public int getSessionId() {
|
||||||
return _sessionId;
|
return _sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param id 0-65535 */
|
/** @param id 0-65535 */
|
||||||
public void setSessionId(int id) {
|
public void setSessionId(int id) {
|
||||||
|
if (id < 0 || id > 65535)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
_sessionId = id;
|
_sessionId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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.11
|
||||||
|
*/
|
||||||
|
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,16 @@ 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.11
|
||||||
|
*/
|
||||||
|
protected void handleHostLookup(HostLookupMessage message) {
|
||||||
|
_context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getReqID(),
|
||||||
|
message.getTimeout(), message.getSessionId(),
|
||||||
|
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?
|
||||||
|
@ -4,32 +4,88 @@
|
|||||||
*/
|
*/
|
||||||
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.data.i2cp.SessionId;
|
||||||
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.11, 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 final SessionId _sessID;
|
||||||
|
|
||||||
|
private static final long DEFAULT_TIMEOUT = 15*1000;
|
||||||
|
|
||||||
public LookupDestJob(RouterContext context, ClientConnectionRunner runner, Hash h) {
|
public LookupDestJob(RouterContext context, ClientConnectionRunner runner, Hash h) {
|
||||||
super(context);
|
this(context, runner, -1, DEFAULT_TIMEOUT, null, h, null);
|
||||||
_runner = runner;
|
}
|
||||||
_hash = h;
|
|
||||||
|
/**
|
||||||
|
* One of h or name non-null
|
||||||
|
* @param reqID must be >= 0 if name != null
|
||||||
|
* @param sessID must non-null if reqID >= 0
|
||||||
|
* @since 0.9.11
|
||||||
|
*/
|
||||||
|
public LookupDestJob(RouterContext context, ClientConnectionRunner runner,
|
||||||
|
long reqID, long timeout, SessionId sessID, Hash h, String name) {
|
||||||
|
super(context);
|
||||||
|
if ((h == null && name == null) ||
|
||||||
|
(h != null && name != null) ||
|
||||||
|
(reqID >= 0 && sessID == null) ||
|
||||||
|
(reqID < 0 && name != null))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
_runner = runner;
|
||||||
|
_reqID = reqID;
|
||||||
|
_timeout = timeout;
|
||||||
|
_sessID = sessID;
|
||||||
|
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 _name != null ?
|
||||||
|
"HostName Lookup for Client" :
|
||||||
|
"LeaseSet Lookup for Client";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() { return "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 +98,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(_sessID, d, _reqID);
|
||||||
|
else
|
||||||
|
msg = new DestReplyMessage(d);
|
||||||
try {
|
try {
|
||||||
_runner.doSend(msg);
|
_runner.doSend(msg);
|
||||||
} catch (I2CPMessageException ime) {}
|
} catch (I2CPMessageException ime) {}
|
||||||
@ -57,8 +117,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(_sessID, HostReplyMessage.RESULT_FAILURE, _reqID);
|
||||||
|
else
|
||||||
|
msg = new DestReplyMessage(_hash);
|
||||||
try {
|
try {
|
||||||
_runner.doSend(msg);
|
_runner.doSend(msg);
|
||||||
} catch (I2CPMessageException ime) {}
|
} catch (I2CPMessageException ime) {}
|
||||||
|
Reference in New Issue
Block a user