/**
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
*
* See...
*
* http://sam.zoy.org/wtfpl/
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and license questions.
*/
package net.i2p.BOB;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
*
* Multiplex listeners for TCP and I2P
*
* @author sponge
*/
public class MUXlisten implements Runnable {
private final NamedDB database, info;
private final Logger _log;
private final I2PSocketManager socketManager;
private final ByteArrayInputStream prikey;
private ThreadGroup tg;
private final String N;
private ServerSocket listener;
private final int backlog = 50; // should this be more? less?
private final boolean go_out;
private final boolean come_in;
private final AtomicBoolean lock;
private final AtomicBoolean lives;
/**
* Constructor Will fail if INPORT is occupied.
*
* @param info DB entry for this tunnel
* @param database master database of tunnels
* @param _log
* @throws net.i2p.I2PException
* @throws java.io.IOException
*/
MUXlisten(AtomicBoolean lock, NamedDB database, NamedDB info, Logger _log) throws I2PException, IOException, RuntimeException {
int port = 0;
InetAddress host = null;
this.lock = lock;
this.tg = null;
this.database = database;
this.info = info;
this._log = _log;
lives = new AtomicBoolean(false);
try {
wlock();
try {
this.info.add("STARTING", Boolean.TRUE);
} finally {
wunlock();
}
Properties Q = new Properties();
rlock();
try {
N = this.info.get("NICKNAME").toString();
prikey = new ByteArrayInputStream((byte[]) info.get("KEYS"));
// Make a new copy so that anything else won't muck with our database.
Properties R = (Properties) info.get("PROPERTIES");
Lifted.copyProperties(R, Q);
this.go_out = info.exists("OUTPORT");
this.come_in = info.exists("INPORT");
if (this.come_in) {
port = Integer.parseInt(info.get("INPORT").toString());
host = InetAddress.getByName(info.get("INHOST").toString());
}
} finally {
runlock();
}
String i2cpHost = Q.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
int i2cpPort = 7654;
String i2cpPortStr = Q.getProperty(I2PClient.PROP_TCP_PORT, "7654");
try {
i2cpPort = Integer.parseInt(i2cpPortStr);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Invalid I2CP port specified [" + i2cpPortStr + "]");
}
if (this.come_in) {
this.listener = new ServerSocket(port, backlog, host);
}
socketManager = I2PSocketManagerFactory.createManager(
prikey, i2cpHost, i2cpPort, Q);
} catch (IOException e) {
// Something went bad.
wlock();
try {
this.info.add("STARTING", Boolean.FALSE);
} finally {
wunlock();
}
throw e;
} catch (RuntimeException e) {
// Something went bad.
wlock();
try {
this.info.add("STARTING", Boolean.FALSE);
} finally {
wunlock();
}
throw e;
} catch (Exception e) {
// Something else went bad.
wlock();
try {
this.info.add("STARTING", Boolean.FALSE);
} finally {
wunlock();
}
e.printStackTrace();
throw new RuntimeException(e);
}
}
private void rlock() {
database.getReadLock();
info.getReadLock();
}
private void runlock() {
info.releaseReadLock();
database.releaseReadLock();
}
private void wlock() {
database.getWriteLock();
info.getWriteLock();
}
private void wunlock() {
info.releaseWriteLock();
database.releaseWriteLock();
}
/**
* MUX sockets, fire off a thread to connect, get destination info, and do I/O
*
*/
public void run() {
I2PServerSocket SS = null;
Thread t = null;
Thread q = null;
try {
wlock();
try {
try {
info.add("RUNNING", Boolean.TRUE);
} catch (Exception e) {
lock.set(false);
return;
}
} catch (Exception e) {
lock.set(false);
return;
} finally {
wunlock();
}
lives.set(true);
lock.set(false);
quit:
{
try {
tg = new ThreadGroup(N);
{
// toss the connections to a new threads.
// will wrap with TCP and UDP when UDP works
if (go_out) {
// I2P -> TCP
SS = socketManager.getServerSocket();
I2Plistener conn = new I2Plistener(SS, socketManager, info, database, _log, lives);
t = new I2PAppThread(tg, conn, "BOBI2Plistener " + N);
t.start();
}
if (come_in) {
// TCP -> I2P
TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log, lives);
q = new I2PAppThread(tg, conn, "BOBTCPlistener " + N);
q.start();
}
wlock();
try {
try {
info.add("STARTING", Boolean.FALSE);
} catch (Exception e) {
break quit;
}
} catch (Exception e) {
break quit;
} finally {
wunlock();
}
boolean spin = true;
while (spin && lives.get()) {
try {
Thread.sleep(1000); //sleep for 1 second
} catch (InterruptedException e) {
break quit;
}
rlock();
try {
try {
spin = info.get("STOPPING").equals(Boolean.FALSE);
} catch (Exception e) {
break quit;
}
} catch (Exception e) {
break quit;
} finally {
runlock();
}
}
} // die
} catch (Exception e) {
// System.out.println("MUXlisten: Caught an exception" + e);
break quit;
}
} // quit
} finally {
lives.set(false);
// Some grace time.
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
try {
wlock();
try {
info.add("STARTING", Boolean.FALSE);
info.add("STOPPING", Boolean.TRUE);
info.add("RUNNING", Boolean.FALSE);
} catch (Exception e) {
lock.set(false);
return;
}
} catch (Exception e) {
} finally {
wunlock();
}
// Start cleanup.
while (!lock.compareAndSet(false, true)) {
// wait
}
if (SS != null) {
try {
SS.close();
} catch (I2PException ex) {
}
}
if (listener != null) {
try {
listener.close();
} catch (IOException e) {
}
}
// Some grace time.
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
// Hopefully nuke stuff here...
{
String groupName = tg.getName();
try {
_log.warn("destroySocketManager " + groupName);
socketManager.destroySocketManager();
_log.warn("destroySocketManager Successful" + groupName);
} catch (Exception e) {
// nop
_log.warn("destroySocketManager Failed" + groupName);
_log.warn(e.toString());
}
}
// zero out everything.
try {
wlock();
try {
info.add("STARTING", Boolean.FALSE);
info.add("STOPPING", Boolean.FALSE);
info.add("RUNNING", Boolean.FALSE);
} catch (Exception e) {
lock.set(false);
return;
} finally {
wunlock();
}
} catch (Exception e) {
}
lock.set(false); // Should we force waiting for all threads??
// Wait around till all threads are collected.
if (tg != null) {
String groupName = tg.getName();
// System.out.println("BOB: MUXlisten: Starting thread collection for: " + groupName);
_log.warn("BOB: MUXlisten: Starting thread collection for: " + groupName);
if (tg.activeCount() + tg.activeGroupCount() != 0) {
// visit(tg, 0, groupName);
int foo = tg.activeCount() + tg.activeGroupCount();
// hopefully no longer needed!
// int bar = lives;
// System.out.println("BOB: MUXlisten: Waiting on threads for " + groupName);
// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + groupName);
// visit(tg, 0, groupName);
// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + groupName + "\n");
// Happily spin forever :-(
while (foo != 0) {
foo = tg.activeCount() + tg.activeGroupCount();
// if (lives != bar && lives != 0) {
// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + groupName);
// visit(tg, 0, groupName);
// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + groupName + "\n");
// }
// bar = lives;
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch (InterruptedException ex) {
// nop
}
}
}
// System.out.println("BOB: MUXlisten: Threads went away. Success: " + groupName);
_log.warn("BOB: MUXlisten: Threads went away. Success: " + groupName);
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;
}
try {
socketManager.destroySocketManager();
} catch (Exception e) {
// nop
}
}
}
// Debugging... None of this is normally used.
/**
* Find the root thread group and print them all.
*
*/
private void visitAllThreads() {
ThreadGroup root = Thread.currentThread().getThreadGroup().getParent();
while (root.getParent() != null) {
root = root.getParent();
}
// Visit each thread group
visit(root, 0, root.getName());
}
/**
* Recursively visits all thread groups under `group' and dumps them.
* @param group ThreadGroup to visit
* @param level Current level
*/
private static void visit(ThreadGroup group, int level, String tn) {
// Get threads in `group'
int numThreads = group.activeCount();
Thread[] threads = new Thread[numThreads * 2];
numThreads = group.enumerate(threads, false);
String indent = "------------------------------------".substring(0, level) + "-> ";
// Enumerate each thread in `group' and print it.
for (int i = 0; i < numThreads; i++) {
// Get thread
Thread thread = threads[i];
System.out.println("BOB: MUXlisten: " + tn + ": " + indent + thread.toString());
}
// Get thread subgroups of `group'
int numGroups = group.activeGroupCount();
ThreadGroup[] groups = new ThreadGroup[numGroups * 2];
numGroups = group.enumerate(groups, false);
// Recursively visit each subgroup
for (int i = 0; i < numGroups; i++) {
visit(groups[i], level + 1, groups[i].getName());
}
}
}