2004-04-08 04:41:54 +00:00
|
|
|
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
|
|
|
* (c) 2003 - 2004 mihi
|
|
|
|
*/
|
|
|
|
package net.i2p.i2ptunnel;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2004-04-20 15:43:04 +00:00
|
|
|
import java.io.InterruptedIOException;
|
2004-04-19 21:47:06 +00:00
|
|
|
import java.net.ConnectException;
|
2004-04-08 04:41:54 +00:00
|
|
|
import java.net.InetAddress;
|
2004-04-19 21:47:06 +00:00
|
|
|
import java.net.NoRouteToHostException;
|
2004-04-08 04:41:54 +00:00
|
|
|
import java.net.ServerSocket;
|
|
|
|
import java.net.Socket;
|
|
|
|
import java.net.UnknownHostException;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Properties;
|
|
|
|
|
|
|
|
import net.i2p.I2PException;
|
2004-09-07 07:17:02 +00:00
|
|
|
import net.i2p.client.I2PSession;
|
|
|
|
import net.i2p.client.I2PSessionException;
|
2004-04-08 04:41:54 +00:00
|
|
|
import net.i2p.client.streaming.I2PSocket;
|
|
|
|
import net.i2p.client.streaming.I2PSocketManager;
|
|
|
|
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
|
|
|
import net.i2p.client.streaming.I2PSocketOptions;
|
|
|
|
import net.i2p.data.Destination;
|
|
|
|
import net.i2p.util.EventDispatcher;
|
2004-04-09 01:22:04 +00:00
|
|
|
import net.i2p.util.I2PThread;
|
2004-04-10 11:39:00 +00:00
|
|
|
import net.i2p.util.Log;
|
2004-04-08 04:41:54 +00:00
|
|
|
|
2004-04-10 11:45:02 +00:00
|
|
|
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
|
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
private static final Log _log = new Log(I2PTunnelClientBase.class);
|
|
|
|
protected Logging l;
|
2004-04-10 11:45:02 +00:00
|
|
|
|
|
|
|
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
2004-04-08 04:41:54 +00:00
|
|
|
|
2004-05-04 08:14:19 +00:00
|
|
|
private static volatile long __clientId = 0;
|
2004-05-19 15:20:55 +00:00
|
|
|
protected long _clientId;
|
2004-04-08 04:41:54 +00:00
|
|
|
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
|
|
|
|
private I2PSocketManager sockMgr;
|
2004-11-15 14:35:16 +00:00
|
|
|
protected List mySockets = new ArrayList();
|
2004-04-08 04:41:54 +00:00
|
|
|
|
|
|
|
protected Destination dest = null;
|
|
|
|
private int localPort;
|
|
|
|
|
|
|
|
private boolean listenerReady = false;
|
|
|
|
|
|
|
|
private ServerSocket ss;
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
private Object startLock = new Object();
|
|
|
|
private boolean startRunning = false;
|
|
|
|
|
|
|
|
private Object closeLock = new Object();
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
private byte[] pubkey;
|
|
|
|
|
|
|
|
private String handlerName;
|
|
|
|
|
2004-11-13 09:59:37 +00:00
|
|
|
private Object conLock = new Object();
|
|
|
|
private int pendingConnections = 0;
|
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
//public I2PTunnelClientBase(int localPort, boolean ownDest,
|
|
|
|
// Logging l) {
|
|
|
|
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
|
|
|
|
//}
|
|
|
|
|
2004-08-03 08:18:10 +00:00
|
|
|
/**
|
|
|
|
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
|
|
|
* badly that we cant create a socketManager
|
|
|
|
*/
|
|
|
|
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l,
|
|
|
|
EventDispatcher notifyThis, String handlerName,
|
|
|
|
I2PTunnel tunnel) throws IllegalArgumentException{
|
2004-07-10 16:59:49 +00:00
|
|
|
super(localPort + " (uninitialized)", notifyThis, tunnel);
|
2004-05-04 08:14:19 +00:00
|
|
|
_clientId = ++__clientId;
|
2004-04-10 11:45:02 +00:00
|
|
|
this.localPort = localPort;
|
|
|
|
this.l = l;
|
2004-05-04 08:14:19 +00:00
|
|
|
this.handlerName = handlerName + _clientId;
|
2004-04-10 11:45:02 +00:00
|
|
|
|
|
|
|
synchronized (sockLock) {
|
|
|
|
if (ownDest) {
|
|
|
|
sockMgr = buildSocketManager();
|
|
|
|
} else {
|
|
|
|
sockMgr = getSocketManager();
|
|
|
|
}
|
|
|
|
}
|
2004-08-03 08:18:10 +00:00
|
|
|
if (sockMgr == null) {
|
|
|
|
l.log("Invalid I2CP configuration");
|
|
|
|
throw new IllegalArgumentException("Socket manager could not be created");
|
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
l.log("I2P session created");
|
|
|
|
|
2004-08-19 17:36:27 +00:00
|
|
|
getTunnel().addSession(sockMgr.getSession());
|
|
|
|
|
2004-04-10 11:45:02 +00:00
|
|
|
Thread t = new I2PThread(this);
|
2004-05-04 08:14:19 +00:00
|
|
|
t.setName("Client " + _clientId);
|
2004-04-10 11:45:02 +00:00
|
|
|
listenerReady = false;
|
|
|
|
t.start();
|
|
|
|
open = true;
|
|
|
|
synchronized (this) {
|
2004-11-21 22:31:33 +00:00
|
|
|
while (!listenerReady && open) {
|
2004-04-10 11:45:02 +00:00
|
|
|
try {
|
|
|
|
wait();
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
// ignore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (open && listenerReady) {
|
|
|
|
l.log("Ready! Port " + getLocalPort());
|
|
|
|
notifyEvent("openBaseClientResult", "ok");
|
|
|
|
} else {
|
2004-11-21 22:31:33 +00:00
|
|
|
l.log("Error listening - please see the logs!");
|
2004-04-10 11:45:02 +00:00
|
|
|
notifyEvent("openBaseClientResult", "error");
|
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static I2PSocketManager socketManager;
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-07-10 16:59:49 +00:00
|
|
|
protected synchronized I2PSocketManager getSocketManager() {
|
|
|
|
return getSocketManager(getTunnel());
|
|
|
|
}
|
|
|
|
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) {
|
2004-09-07 07:17:02 +00:00
|
|
|
if (socketManager != null) {
|
|
|
|
I2PSession s = socketManager.getSession();
|
|
|
|
if ( (s == null) || (s.isClosed()) ) {
|
|
|
|
_log.info("Building a new socket manager since the old one closed [s=" + s + "]");
|
|
|
|
socketManager = buildSocketManager(tunnel);
|
|
|
|
} else {
|
|
|
|
_log.info("Not building a new socket manager since the old one is open [s=" + s + "]");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_log.info("Building a new socket manager since there is no other one");
|
2004-07-10 16:59:49 +00:00
|
|
|
socketManager = buildSocketManager(tunnel);
|
2004-04-10 11:45:02 +00:00
|
|
|
}
|
|
|
|
return socketManager;
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-07-10 16:59:49 +00:00
|
|
|
protected I2PSocketManager buildSocketManager() {
|
|
|
|
return buildSocketManager(getTunnel());
|
|
|
|
}
|
|
|
|
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel) {
|
2004-04-10 11:45:02 +00:00
|
|
|
Properties props = new Properties();
|
2004-07-10 16:59:49 +00:00
|
|
|
if (tunnel == null)
|
|
|
|
props.putAll(System.getProperties());
|
|
|
|
else
|
|
|
|
props.putAll(tunnel.getClientOptions());
|
2004-08-03 08:18:10 +00:00
|
|
|
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
|
|
|
|
if (sockManager == null) return null;
|
2004-06-28 13:23:24 +00:00
|
|
|
sockManager.setName("Client");
|
|
|
|
return sockManager;
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
public final int getLocalPort() {
|
|
|
|
return localPort;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected final InetAddress getListenHost(Logging l) {
|
2004-04-10 11:45:02 +00:00
|
|
|
try {
|
2004-08-03 08:18:10 +00:00
|
|
|
return InetAddress.getByName(getTunnel().listenHost);
|
2004-04-10 11:45:02 +00:00
|
|
|
} catch (UnknownHostException uhe) {
|
2004-08-03 08:18:10 +00:00
|
|
|
l.log("Could not find listen host to bind to [" + getTunnel().host + "]");
|
2004-04-10 11:45:02 +00:00
|
|
|
_log.error("Error finding host to bind", uhe);
|
|
|
|
notifyEvent("openBaseClientResult", "error");
|
|
|
|
return null;
|
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
/**
|
|
|
|
* Actually start working on incoming connections. *Must* be
|
|
|
|
* called by derived classes after initialization.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public final void startRunning() {
|
2004-04-10 11:45:02 +00:00
|
|
|
synchronized (startLock) {
|
|
|
|
startRunning = true;
|
|
|
|
startLock.notify();
|
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
|
2004-04-08 04:41:54 +00:00
|
|
|
/**
|
|
|
|
* create the default options (using the default timeout, etc)
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private I2PSocketOptions getDefaultOptions() {
|
2004-11-16 22:11:11 +00:00
|
|
|
Properties defaultOpts = getTunnel().getClientOptions();
|
|
|
|
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
|
|
|
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
|
|
|
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
2004-04-10 11:45:02 +00:00
|
|
|
return opts;
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new I2PSocket towards to the specified destination,
|
|
|
|
* adding it to the list of connections actually managed by this
|
|
|
|
* tunnel.
|
|
|
|
*
|
|
|
|
* @param dest The destination to connect to
|
|
|
|
* @return a new I2PSocket
|
|
|
|
*/
|
2004-04-20 15:43:04 +00:00
|
|
|
public I2PSocket createI2PSocket(Destination dest) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
2004-04-10 11:45:02 +00:00
|
|
|
return createI2PSocket(dest, getDefaultOptions());
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new I2PSocket towards to the specified destination,
|
|
|
|
* adding it to the list of connections actually managed by this
|
|
|
|
* tunnel.
|
|
|
|
*
|
|
|
|
* @param dest The destination to connect to
|
|
|
|
* @param opt Option to be used to open when opening the socket
|
|
|
|
* @return a new I2PSocket
|
2004-04-19 21:47:06 +00:00
|
|
|
*
|
|
|
|
* @throws ConnectException if the peer refuses the connection
|
|
|
|
* @throws NoRouteToHostException if the peer is not found or not reachable
|
2004-04-20 15:43:04 +00:00
|
|
|
* @throws InterruptedIOException if the connection timeouts
|
2004-04-19 21:47:06 +00:00
|
|
|
* @throws I2PException if there is some other I2P-related problem
|
2004-04-08 04:41:54 +00:00
|
|
|
*/
|
2004-04-20 15:43:04 +00:00
|
|
|
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
2004-04-10 11:45:02 +00:00
|
|
|
I2PSocket i2ps;
|
2004-04-08 04:41:54 +00:00
|
|
|
|
2004-05-04 08:14:19 +00:00
|
|
|
i2ps = sockMgr.connect(dest, opt);
|
2004-04-10 11:45:02 +00:00
|
|
|
synchronized (sockLock) {
|
|
|
|
mySockets.add(i2ps);
|
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
|
2004-04-10 11:45:02 +00:00
|
|
|
return i2ps;
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public final void run() {
|
2004-04-10 11:45:02 +00:00
|
|
|
try {
|
|
|
|
InetAddress addr = getListenHost(l);
|
2004-11-21 22:31:33 +00:00
|
|
|
if (addr == null) {
|
|
|
|
open = false;
|
|
|
|
synchronized (this) {
|
|
|
|
notifyAll();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
ss = new ServerSocket(localPort, 0, addr);
|
|
|
|
|
|
|
|
// If a free port was requested, find out what we got
|
|
|
|
if (localPort == 0) {
|
|
|
|
localPort = ss.getLocalPort();
|
|
|
|
}
|
|
|
|
notifyEvent("clientLocalPort", new Integer(ss.getLocalPort()));
|
2004-08-03 08:18:10 +00:00
|
|
|
l.log("Listening for clients on port " + localPort + " of " + getTunnel().listenHost);
|
2004-04-10 11:45:02 +00:00
|
|
|
|
|
|
|
// Notify constructor that port is ready
|
|
|
|
synchronized (this) {
|
|
|
|
listenerReady = true;
|
|
|
|
notify();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait until we are authorized to process data
|
|
|
|
synchronized (startLock) {
|
|
|
|
while (!startRunning) {
|
|
|
|
try {
|
|
|
|
startLock.wait();
|
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
Socket s = ss.accept();
|
|
|
|
manageConnection(s);
|
|
|
|
}
|
|
|
|
} catch (IOException ex) {
|
2004-11-21 22:31:33 +00:00
|
|
|
_log.error("Error listening for connections on " + localPort, ex);
|
2004-04-10 11:45:02 +00:00
|
|
|
notifyEvent("openBaseClientResult", "error");
|
2004-11-15 14:35:16 +00:00
|
|
|
synchronized (sockLock) {
|
|
|
|
mySockets.clear();
|
|
|
|
}
|
2004-11-21 22:31:33 +00:00
|
|
|
open = false;
|
|
|
|
synchronized (this) {
|
|
|
|
notifyAll();
|
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manage the connection just opened on the specified socket
|
|
|
|
*
|
|
|
|
* @param s Socket to take care of
|
|
|
|
*/
|
|
|
|
protected void manageConnection(Socket s) {
|
2004-11-13 09:59:37 +00:00
|
|
|
boolean useBlocking = false;
|
|
|
|
synchronized (conLock) {
|
|
|
|
pendingConnections++;
|
|
|
|
if (pendingConnections > 5)
|
|
|
|
useBlocking = true;
|
|
|
|
}
|
|
|
|
if (useBlocking)
|
|
|
|
clientConnectionRun(s);
|
|
|
|
else
|
|
|
|
new ClientConnectionRunner(s, handlerName);
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean close(boolean forced) {
|
2004-04-10 11:45:02 +00:00
|
|
|
if (!open) return true;
|
|
|
|
// FIXME: here we might have to wait quite a long time if
|
|
|
|
// there is a connection attempt atm. But without waiting we
|
|
|
|
// might risk to create an orphan socket. Would be better
|
|
|
|
// to return with an error in that situation quickly.
|
|
|
|
synchronized (sockLock) {
|
|
|
|
mySockets.retainAll(sockMgr.listSockets());
|
|
|
|
if (!forced && mySockets.size() != 0) {
|
|
|
|
l.log("There are still active connections!");
|
|
|
|
_log.debug("can't close: there are still active connections!");
|
|
|
|
for (Iterator it = mySockets.iterator(); it.hasNext();) {
|
|
|
|
l.log("->" + it.next());
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2004-09-07 07:17:02 +00:00
|
|
|
I2PSession session = sockMgr.getSession();
|
|
|
|
if (session != null) {
|
|
|
|
getTunnel().removeSession(session);
|
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
l.log("Closing client " + toString());
|
|
|
|
try {
|
|
|
|
if (ss != null) ss.close();
|
|
|
|
} catch (IOException ex) {
|
|
|
|
ex.printStackTrace();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
l.log("Client closed.");
|
|
|
|
open = false;
|
|
|
|
return true;
|
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static void closeSocket(Socket s) {
|
2004-04-10 11:45:02 +00:00
|
|
|
try {
|
|
|
|
s.close();
|
|
|
|
} catch (IOException ex) {
|
|
|
|
_log.error("Could not close socket", ex);
|
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
2004-05-07 03:33:23 +00:00
|
|
|
private static volatile long __runnerId = 0;
|
|
|
|
|
2004-04-09 01:22:04 +00:00
|
|
|
public class ClientConnectionRunner extends I2PThread {
|
2004-04-10 11:45:02 +00:00
|
|
|
private Socket s;
|
|
|
|
|
|
|
|
public ClientConnectionRunner(Socket s, String name) {
|
|
|
|
this.s = s;
|
2004-05-07 03:33:23 +00:00
|
|
|
setName(name + '.' + (++__runnerId));
|
2004-04-10 11:45:02 +00:00
|
|
|
start();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
|
|
|
clientConnectionRun(s);
|
2004-11-13 09:59:37 +00:00
|
|
|
synchronized (conLock) {
|
|
|
|
pendingConnections--;
|
|
|
|
}
|
2004-04-10 11:45:02 +00:00
|
|
|
}
|
2004-04-08 04:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manage a connection in a separate thread. This only works if
|
|
|
|
* you do not override manageConnection()
|
|
|
|
*/
|
|
|
|
protected abstract void clientConnectionRun(Socket s);
|
2004-04-19 21:47:06 +00:00
|
|
|
}
|