I2P builds up a new net inside the usual internet, connecting nodes together
+ via encrypted connections.
+ It is a JAVA prgram with its most used part (the encryption of the data) written
+ in handoptimized assembler code.
+ It will use your bandwith, your RAM and your CPU. It will use them all up if you
+ do not limit it.
+ I2P will route unknown traffic through your node, even stuff you dislike.
+ As that data is encrypted, nobody knows whats data went to or drom your node.
+
+
+
+ First, ALWAYS use the latest stable release.
+ Development releases are called "mtn version" and are marked with a -, e.g.
+ 0.6.5-1. Those are usually useable by all but could do harm to your I2P
+ experience.
+ You can get the latest MTN builds from my eepsite echelon.i2p, but always
+ remember: I built them, you need to trust me not to changed the code!
+ After you get the right "i2pupdate.zip" file, put that file into the I2P
+ directory and hit restart on the router console http://127.0.0.1:7657.
+ Do NOT deflate the zip file!
+
+
+
+ I2P is very dynamic - after startup it tries to get known to other I2P routers
+ and measures their speed - you need to wait some 10-120 minutes until your
+ I2P router knows enough other ones to obtain full power of I2P.
+
+
+
Filesharing
+
+
+ I2P is able to do anonymous filesharing.
+ But as there are NO gateways between real net and I2P, you can only share/
+ download torrents from within I2P. Look e.g. postman.i2p or planet.i2p.
+ You CANNOT use azureus, utorrent or any other usual client.
+ You cannot download anonymous torrents from mininova, piratebay or else.
+ You need to use I2P internal torrents and I2P aware programs like
+ I2Psnark (builtin, suitable for 1-20 torrents)
+ I2PRufus (external, python, high CPU load, suitable >20 torrents) http://echelon.i2p/i2prufus
+ I2P-BT (external, python)
+ I2PsnarkXL (mod of I2Psnark made by fwd)
+
+
+
+ There are also gnutella and edonkey clients:
+ i2phex for gnutella (http://echelon.i2p/i2phex)
+ imule for edonkey (http://echelon.i2p/imule)
+
+
+
+ Remember, as I2P uses other routers to route your traffic via 1-6 other PCs,
+ your transferrates in P2P are slower than in usual internet.
+ But you are anonymous, no one can easily (within 2 months-2 years) get your IP!
+ torrents inside of I2P reaches up to 50 kb/sec, usual are 10-20 kb/sec per torrent
+ i2phex reaches up to 20 kb/sec, usually 5-10 kb/sec
+ imule in times reaches 10 kb/sec, usually 5-10 kb/sec
+
+
+
+ In I2PHex and imule you can just tell "share file or directory", in torrent
+ you need to create a .torrent file and upload that (and ONLY that small .torrent)
+ file to the trackers like tracker.postman.i2p/
+
+
+
+ I2P is a smaller net (1000 users) which grows slowly. As of which amount of shared
+ data will slowly rise.
+
+
+
+ I2P is anonymous and it does not censor - there is no administrator.
+ There IS unwanted stuff like kiddyporn, nazism, or else.
+ If you dislike all this, do not use I2P.
+ There is NO way to prohibite this stuff to appear in a anonymous net like I2P.
+ (as that stuff is available shows the anonymity and transfer function of I2P
+ is working well enough)
+ You can delete the destinations in question from your local hosts.txt file (or
+ deface them) which will partly prevent you to reach those bad sies by accident.
+
+
+
Internet (the websites)
+
+
+ Only one outproxy (gateway I2P - webpages in usual Internet) is working.
+ It is NOT official from I2P, I2P does work without.
+ If that outproxy is slow, offline, gone,.. I2P still works on and cannot do
+ anything to change that failure.
+ That outproxy translates usual internet webpages into the I2P net and you can
+ reach them via your Router.
+ The best way for usual webpages is TOR, not that outproxy.
+ Remember: the owner of the outproxy got ALL traffic from all I2P users
+ visiting Internet pages and will risk that into the police!
+
+
+
+ This proxy is false.i2p. In newer I2P routers it is enabled, but not in
+ older ones. Go to http://127.0.0.1:7657/i2ptunnel/index.jsp tunnels page and
+ click on the eepProxy tunnel. Change the entry for the "Outproxies" to false.i2p
+ and save. On the tunnels page, stop the epproxy tunnel and start it again,
+ now your router will use the false.i2p outproxy.
+
+
+
+ No other (known) gateways are setup and running. No one we know will run
+ the gateway for torrent or any other P2P data (and risk his life).
+
+
+
Bandwidth
+
+
http://127.0.0.1:7657/config.jsp
+
+
Setup your bandwith wisely. Know your linespeed!
+
+
+ E.g. most common terms are:
+ 1Mbit = roughly 100 kbyte/sec
+ 10 MBit = roughly 1100 kbyte/sec
+ 512 kbit = roughly 50 kbyte/sec
+ or in germany:
+ 16000er = roughly 1500 kbyte/sec
+ 6000er = roughly 600 kbyte/sec
+ 1000er = roughly 100 kb/sec
+
+
+
+ Set your bandwith limits to 10% under your line speed and burst rate to
+ your line speed.
+ Set the bandwith share percentage to:
+ >80% if lowest bandwith setting is >50k
+ >50% if lowest bandwith setting is >30k
+ >20% if lowest bandwith setting is >16k
+
+
+
There is no shared bandwith under 16k.
+
+
+ Limit your participating tunnels (shared bandwith) on:
+ http://127.0.0.1:7657/configadvanced.jsp
+ with the line:
+ router.maxParticipatingTunnels=500
+
+
+
+ 2000 is for roughly 600 kb/sec - very high value with high CPU load
+ 1000 is for roughly 300 kb/sec
+ 600 is a good value for 150-200kb/sec
+ 300 is roughly 90 kb/sec
+ 150 roughly 50 kb/sec
+ Remember: even failed tunnel requests will result in a part tunnel on the hops in between!
+ Those said, there are far more part tunnels unused than used in the live net under load, which
+ results in slower bandwith per tunnel in the end.
+ It is wise to first limit the bandwith and afterwards the part tunnels, e.g. set some more part
+ tunnels and let I2P reach the bandwith limit instead of the part tunnels limit!
+
+
+
What is shared bandwidth?
+
+
+ I2P transports your date from the client to the server through 1-6 hops
+ (other I2P routers). Each of this hops sees the data from you as "participating
+ tunnel" - which is the shared bandwith of them.
+ With this in mind, I2P needs some amount of this shared bandwith at some
+ amount of routers.
+ Share as much as you are able of - others will thank you!
+
+
+
+ With the "share percentage" set like above, you will obtain enough speed for
+ your own traffic and obtain some participating tunnels (if >16kb/sec) with some
+ noise traffic to hide your traffic in the stream.
+
+
+
+ With release 0.6.5 there is some method to prefer your own traffic ahead
+ of shared traffic which will result in better experience to you!
+
+
+
Addressbook
+
+
+ I2P uses a local addressbook to link short DNS names with the internal used 512bit
+ hashes (which are destination IDs).
+ Those links are saved insside the hosts.txt and userhosts.txt files in the i2p
+ directory.
+ Hosts which are not in those files cannot be reached via the short DNS names
+ and a error message with "jumper links" will appear. Those links will ask
+ some hosts services and forward to the correct site (if the site is known to them).
+ Those hosts services just made a form to add new "hosts" and those results public
+ available.
+ You can subscribe to those hosts service and let your hosts.txt file be updated
+ automatic. Go to http://127.0.0.1:7657/susidns/subscriptions.jsp SusiDNS
+ and enter the hosts services into the textbox (and save afterwards):
+ http://www.i2p2.i2p/hosts.txt
+ http://stats.i2p/cgi-bin/newhosts.txt
+ http://tino.i2p/hosts.txt
+ http://i2host.i2p/cgi-bin/i2hostag
+ You can add one of them, two or all.
+ SusiDNS will now ask those hosts for new entries to the hosts.txt and those
+ will be added to your hosts.txt. The userhosts.txt will ONLY be updated by
+ yourself (the user) and not be published into the net!
+ Remember, names once set could not be changed! If you loose your key (destination
+ ID) to your eepsite, service,..., there is no way to change the linking
+ between the DNS name and the (lost) destination ID automatic! Only manual by each
+ user itself - great topic to discuss of need to renew DNS hostnames.
+ As this subscription will not update old entries, you can "deface" unwanted
+ eepsites with a false key and if you hit the bad name in browser by accident,
+ you will not see the bad stuff!
+
+
+
Out of Memory errors
+
+
+ If your router hits the Out of Memory error - check your logs!
+ Usual point for OOM are to much torrents in i2psnark - i2psnark is a real
+ memory hogg and >10 torrents it requiers hell a lot of memory!
+
+
+
+ Maybe it is possible for you to increase the wrapper memory config.
+ This ONLY works if you start the I2P service restartable with console
+ (on Windows).
+ In I2P directory edit the wrapper.config file and change the values:
+ wrapper.java.maxmemory=256 (or even to 512, IF possible)
+ Afterwards shutdown I2P complete (the service) and restart it.
+
+
+
Blocklists
+
+
+ Sometimes attackers trying to flood the I2P net and try to do some harm.
+ And some folks setting localnet IPs as their internet reachable address.
+ To prevent those bad router to harm the local router, I2P implemented
+ a local blocklist system. It is NOT integrated automatic as it could
+ really harm your I2P experience if setup the wrong way.
+ The way to enable blocklists is:
+ Get the file http://zzz.i2p/files/blocklist.txt and copy this file into the
+ I2P directory.
+ On http://127.0.0.1:7657/configadvanced.jsp set the option
+ router.blocklist.enable=true - click on Apply and restart the router
+ with the restart link left on router console.
+ The blockfile.txt file follows a special order, you´ll get it if you read it.
+ The first entry is the reason to be shown on http://127.0.0.1:7657/profiles.jsp
+ at the bottom in the shitlist section.
+ The second entry is the IP or the dest ID of a router.
+ Right now there are only private subnets in the blocklist AND one chinese router
+ which floods the floodfill DB while restarting every few minutes with a different
+ router ID and far to less bandwith for being a floodfill router.
+
+
+
(By echelon -- echelon.i2p )
+
+
diff --git a/apps/desktopgui/desktopgui/resources/logo/logo.jpg b/apps/desktopgui/desktopgui/resources/logo/logo.jpg
new file mode 100644
index 0000000000..f1b5ccfc87
Binary files /dev/null and b/apps/desktopgui/desktopgui/resources/logo/logo.jpg differ
diff --git a/apps/desktopgui/desktopgui/resources/logo/logo_green.jpg b/apps/desktopgui/desktopgui/resources/logo/logo_green.jpg
new file mode 100644
index 0000000000..d558efcea3
Binary files /dev/null and b/apps/desktopgui/desktopgui/resources/logo/logo_green.jpg differ
diff --git a/apps/desktopgui/desktopgui/resources/logo/logo_orange.jpg b/apps/desktopgui/desktopgui/resources/logo/logo_orange.jpg
new file mode 100644
index 0000000000..d131f21b62
Binary files /dev/null and b/apps/desktopgui/desktopgui/resources/logo/logo_orange.jpg differ
diff --git a/apps/desktopgui/desktopgui/resources/logo/logo_red.jpg b/apps/desktopgui/desktopgui/resources/logo/logo_red.jpg
new file mode 100644
index 0000000000..c1a60b8bbe
Binary files /dev/null and b/apps/desktopgui/desktopgui/resources/logo/logo_red.jpg differ
diff --git a/apps/desktopgui/lib/appframework.jar b/apps/desktopgui/lib/appframework.jar
new file mode 100644
index 0000000000..0b8ff0145f
Binary files /dev/null and b/apps/desktopgui/lib/appframework.jar differ
diff --git a/apps/desktopgui/lib/swing-worker.jar b/apps/desktopgui/lib/swing-worker.jar
new file mode 100644
index 0000000000..bcdd9d9102
Binary files /dev/null and b/apps/desktopgui/lib/swing-worker.jar differ
diff --git a/apps/desktopgui/manifest.mf b/apps/desktopgui/manifest.mf
new file mode 100644
index 0000000000..328e8e5bc3
--- /dev/null
+++ b/apps/desktopgui/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/apps/desktopgui/nbproject/build-impl.xml b/apps/desktopgui/nbproject/build-impl.xml
new file mode 100644
index 0000000000..f8fea458d1
--- /dev/null
+++ b/apps/desktopgui/nbproject/build-impl.xml
@@ -0,0 +1,629 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+
+
+
+
+
+ java -cp "${run.classpath.with.dist.jar}" ${main.class}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ java -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/desktopgui/nbproject/genfiles.properties b/apps/desktopgui/nbproject/genfiles.properties
new file mode 100644
index 0000000000..1b326007c7
--- /dev/null
+++ b/apps/desktopgui/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=c4b345cd
+build.xml.script.CRC32=9785bb9a
+build.xml.stylesheet.CRC32=be360661
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=c4b345cd
+nbproject/build-impl.xml.script.CRC32=74d3fda2
+nbproject/build-impl.xml.stylesheet.CRC32=487672f9
diff --git a/apps/desktopgui/nbproject/project.properties b/apps/desktopgui/nbproject/project.properties
new file mode 100644
index 0000000000..b7a05f8ed6
--- /dev/null
+++ b/apps/desktopgui/nbproject/project.properties
@@ -0,0 +1,77 @@
+application.desc=An anonymous communication network.
+application.homepage=http://www.i2p2.de
+application.title=I2P Desktop GUI
+application.vendor=I2P Developers
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/desktopgui.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+excludes=
+file.reference.appframework.jar=lib/appframework.jar
+file.reference.i2p.jar=../../core/java/build/i2p.jar
+file.reference.router.jar=../../router/java/build/router.jar
+file.reference.swing-worker.jar=lib/swing-worker.jar
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${file.reference.router.jar}:\
+ ${file.reference.appframework.jar}:\
+ ${file.reference.swing-worker.jar}:\
+ ${file.reference.i2p.jar}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.source=1.6
+javac.target=1.6
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}:\
+ ${libs.junit.classpath}:\
+ ${libs.junit_4.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.encoding.used=${javadoc.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+jnlp.codebase.type=local
+jnlp.codebase.url=file:/home/mathias/Documenten/Programmeren/i2p_monotone/repo/i2p.i2p/apps/desktopgui/dist/
+jnlp.enabled=false
+jnlp.offline-allowed=false
+jnlp.signed=false
+main.class=desktopgui.Main
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project
+# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
+# or test-sys-prop.name=value to set system properties for unit tests):
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/apps/desktopgui/nbproject/project.xml b/apps/desktopgui/nbproject/project.xml
new file mode 100644
index 0000000000..09409a64cd
--- /dev/null
+++ b/apps/desktopgui/nbproject/project.xml
@@ -0,0 +1,19 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ desktopgui
+ 1.6.5
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application b/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application
new file mode 100644
index 0000000000..6cd2ac1fb1
--- /dev/null
+++ b/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application
@@ -0,0 +1 @@
+desktopgui.Main
\ No newline at end of file
diff --git a/apps/desktopgui/src/desktopgui/GUIVersion.java b/apps/desktopgui/src/desktopgui/GUIVersion.java
new file mode 100644
index 0000000000..7bde544de8
--- /dev/null
+++ b/apps/desktopgui/src/desktopgui/GUIVersion.java
@@ -0,0 +1,14 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package desktopgui;
+
+/**
+ *
+ * @author mathias
+ */
+public class GUIVersion {
+ public static final String VERSION = "0.0.1.1";
+}
diff --git a/apps/desktopgui/src/desktopgui/Main.java b/apps/desktopgui/src/desktopgui/Main.java
new file mode 100644
index 0000000000..9d9708c8dd
--- /dev/null
+++ b/apps/desktopgui/src/desktopgui/Main.java
@@ -0,0 +1,109 @@
+package desktopgui;
+
+/*
+ * Main.java
+ */
+
+
+
+import gui.Tray;
+import gui.SpeedSelector;
+import java.awt.SystemTray;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import org.jdesktop.application.Application;
+import org.jdesktop.application.SingleFrameApplication;
+import persistence.PropertyManager;
+
+/**
+ * The main class of the application.
+ */
+public class Main extends SingleFrameApplication {
+
+ /**
+ * At startup create and show the main frame of the application.
+ */
+ @Override protected void startup() {
+ Properties props = PropertyManager.loadProps();
+
+ //First load: present screen with information (to help choose I2P settings)
+ if(props.getProperty(FIRSTLOAD).equals("true")) {
+ props.setProperty(FIRSTLOAD, "false");
+ PropertyManager.saveProps(props);
+ new SpeedSelector(); //Start speed selector GUI
+ }
+
+ if(SystemTray.isSupported()) {
+ tray = new Tray();
+ }
+ else { //Alternative if SystemTray is not supported on the platform
+ }
+ }
+
+ /**
+ * This method is to initialize the specified window by injecting resources.
+ * Windows shown in our application come fully initialized from the GUI
+ * builder, so this additional configuration is not needed.
+ */
+ @Override protected void configureWindow(java.awt.Window root) {
+ }
+
+ /**
+ * A convenient static getter for the application instance.
+ * @return the instance of Main
+ */
+ public static Main getApplication() {
+ return Application.getInstance(Main.class);
+ }
+
+ /**
+ * Main method launching the application.
+ */
+ public static void main(String[] args) {
+ System.setProperty("java.awt.headless", "false"); //Make sure I2P is running in GUI mode for our application
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (ClassNotFoundException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (InstantiationException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (IllegalAccessException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (UnsupportedLookAndFeelException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ Main main = getApplication();
+ main.launchForeverLoop();
+ main.startup();
+ }
+
+ /**
+ * Avoids the app terminating because no Window is opened anymore.
+ * More info: http://java.sun.com/javase/6/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown
+ */
+ public void launchForeverLoop() {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ Object o = new Object();
+ synchronized (o) {
+ o.wait();
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ };
+ Thread t = new Thread(r);
+ t.setDaemon(false);
+ t.start();
+ }
+
+ private Tray tray = null;
+ ///Indicates if this is the first time the application loads
+ ///(is only true at the very start of loading the first time!)
+ private static final String FIRSTLOAD = "firstLoad";
+}
diff --git a/apps/desktopgui/src/desktopgui/resources/Main.properties b/apps/desktopgui/src/desktopgui/resources/Main.properties
new file mode 100644
index 0000000000..fb7218ceae
--- /dev/null
+++ b/apps/desktopgui/src/desktopgui/resources/Main.properties
@@ -0,0 +1,11 @@
+# Application global resources
+
+Application.name = desktopgui
+Application.title = I2P Desktop GUI
+Application.version = 0.7.1
+Application.vendor = I2P Developers
+Application.homepage = http://www.i2p2.de
+Application.description = An anonymous communication network.
+Application.vendorId = I2P
+Application.id = desktopgui
+Application.lookAndFeel = system
diff --git a/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties b/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties
new file mode 100644
index 0000000000..81107aca91
--- /dev/null
+++ b/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties
@@ -0,0 +1,11 @@
+# Application global resources
+
+Application.name = desktopgui
+Application.title = I2P Desktop GUI
+Application.version = 0.7.1
+Application.vendor = I2P Ontwikkelaars
+Application.homepage = http://www.i2p2.de
+Application.description = Een anoniem communicatienetwerk.
+Application.vendorId = I2P
+Application.id = desktopgui
+Application.lookAndFeel = system
diff --git a/apps/desktopgui/src/gui/GeneralConfiguration.form b/apps/desktopgui/src/gui/GeneralConfiguration.form
new file mode 100644
index 0000000000..df02e3d967
--- /dev/null
+++ b/apps/desktopgui/src/gui/GeneralConfiguration.form
@@ -0,0 +1,517 @@
+
+
+
diff --git a/apps/desktopgui/src/gui/GeneralConfiguration.java b/apps/desktopgui/src/gui/GeneralConfiguration.java
new file mode 100644
index 0000000000..912026bf12
--- /dev/null
+++ b/apps/desktopgui/src/gui/GeneralConfiguration.java
@@ -0,0 +1,376 @@
+/*
+ * GeneralConfiguration.java
+ *
+ * Created on 10 april 2009, 19:04
+ */
+
+package gui;
+
+/**
+ *
+ * @author mathias
+ */
+public class GeneralConfiguration extends javax.swing.JFrame {
+
+ /** Creates new form GeneralConfiguration */
+ public GeneralConfiguration() {
+ initComponents();
+ this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ this.setVisible(true);
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ jPanel1 = new javax.swing.JPanel();
+ cancel = new javax.swing.JToggleButton();
+ ok = new javax.swing.JToggleButton();
+ jTabbedPane1 = new javax.swing.JTabbedPane();
+ jPanel2 = new javax.swing.JPanel();
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ jTextField1 = new javax.swing.JTextField();
+ jTextField2 = new javax.swing.JTextField();
+ jComboBox1 = new javax.swing.JComboBox();
+ jComboBox2 = new javax.swing.JComboBox();
+ jLabel3 = new javax.swing.JLabel();
+ jLabel4 = new javax.swing.JLabel();
+ jTextField3 = new javax.swing.JTextField();
+ jTextField4 = new javax.swing.JTextField();
+ jLabel5 = new javax.swing.JLabel();
+ jLabel6 = new javax.swing.JLabel();
+ jLabel7 = new javax.swing.JLabel();
+ jPanel3 = new javax.swing.JPanel();
+ jLabel8 = new javax.swing.JLabel();
+ jRadioButton1 = new javax.swing.JRadioButton();
+ jRadioButton2 = new javax.swing.JRadioButton();
+ jRadioButton3 = new javax.swing.JRadioButton();
+ jToggleButton1 = new javax.swing.JToggleButton();
+ jToggleButton2 = new javax.swing.JToggleButton();
+ jToggleButton3 = new javax.swing.JToggleButton();
+ jPanel4 = new javax.swing.JPanel();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ jScrollPane2 = new javax.swing.JScrollPane();
+ jLabel9 = new javax.swing.JLabel();
+ jLabel10 = new javax.swing.JLabel();
+ jLabel11 = new javax.swing.JLabel();
+ jPanel5 = new javax.swing.JPanel();
+ jPanel6 = new javax.swing.JPanel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ setName("Form"); // NOI18N
+
+ jPanel1.setName("jPanel1"); // NOI18N
+
+ org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(GeneralConfiguration.class);
+ cancel.setText(resourceMap.getString("cancel.text")); // NOI18N
+ cancel.setName("cancel"); // NOI18N
+
+ ok.setText(resourceMap.getString("ok.text")); // NOI18N
+ ok.setName("ok"); // NOI18N
+
+ javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
+ .addContainerGap(475, Short.MAX_VALUE)
+ .addComponent(ok)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cancel)
+ .addContainerGap())
+ );
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(cancel)
+ .addComponent(ok))
+ .addContainerGap(14, Short.MAX_VALUE))
+ );
+
+ jTabbedPane1.setName("jTabbedPane1"); // NOI18N
+
+ jPanel2.setName("jPanel2"); // NOI18N
+ jPanel2.setLayout(null);
+
+ jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
+ jLabel1.setName("jLabel1"); // NOI18N
+ jPanel2.add(jLabel1);
+ jLabel1.setBounds(20, 20, 140, 30);
+
+ jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
+ jLabel2.setName("jLabel2"); // NOI18N
+ jPanel2.add(jLabel2);
+ jLabel2.setBounds(20, 60, 140, 30);
+
+ jTextField1.setText(resourceMap.getString("jTextField1.text")); // NOI18N
+ jTextField1.setName("jTextField1"); // NOI18N
+ jPanel2.add(jTextField1);
+ jTextField1.setBounds(160, 20, 77, 27);
+
+ jTextField2.setText(resourceMap.getString("jTextField2.text")); // NOI18N
+ jTextField2.setName("jTextField2"); // NOI18N
+ jPanel2.add(jTextField2);
+ jTextField2.setBounds(160, 60, 77, 27);
+
+ jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
+ jComboBox1.setName("jComboBox1"); // NOI18N
+ jPanel2.add(jComboBox1);
+ jComboBox1.setBounds(240, 20, 78, 27);
+
+ jComboBox2.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
+ jComboBox2.setName("jComboBox2"); // NOI18N
+ jPanel2.add(jComboBox2);
+ jComboBox2.setBounds(240, 60, 78, 27);
+
+ jLabel3.setText(resourceMap.getString("jLabel3.text")); // NOI18N
+ jLabel3.setName("jLabel3"); // NOI18N
+ jPanel2.add(jLabel3);
+ jLabel3.setBounds(330, 20, 97, 30);
+
+ jLabel4.setText(resourceMap.getString("jLabel4.text")); // NOI18N
+ jLabel4.setName("jLabel4"); // NOI18N
+ jPanel2.add(jLabel4);
+ jLabel4.setBounds(330, 60, 97, 30);
+
+ jTextField3.setText(resourceMap.getString("jTextField3.text")); // NOI18N
+ jTextField3.setName("jTextField3"); // NOI18N
+ jPanel2.add(jTextField3);
+ jTextField3.setBounds(440, 20, 60, 27);
+
+ jTextField4.setText(resourceMap.getString("jTextField4.text")); // NOI18N
+ jTextField4.setName("jTextField4"); // NOI18N
+ jPanel2.add(jTextField4);
+ jTextField4.setBounds(440, 60, 60, 27);
+
+ jLabel5.setText(resourceMap.getString("jLabel5.text")); // NOI18N
+ jLabel5.setName("jLabel5"); // NOI18N
+ jPanel2.add(jLabel5);
+ jLabel5.setBounds(510, 20, 19, 30);
+
+ jLabel6.setText(resourceMap.getString("jLabel6.text")); // NOI18N
+ jLabel6.setName("jLabel6"); // NOI18N
+ jPanel2.add(jLabel6);
+ jLabel6.setBounds(510, 60, 19, 30);
+
+ jLabel7.setText(resourceMap.getString("jLabel7.text")); // NOI18N
+ jLabel7.setName("jLabel7"); // NOI18N
+ jPanel2.add(jLabel7);
+ jLabel7.setBounds(20, 100, 520, 70);
+
+ jTabbedPane1.addTab(resourceMap.getString("jPanel2.TabConstraints.tabTitle"), jPanel2); // NOI18N
+
+ jPanel3.setName("jPanel3"); // NOI18N
+
+ jLabel8.setText(resourceMap.getString("jLabel8.text")); // NOI18N
+ jLabel8.setName("jLabel8"); // NOI18N
+
+ jRadioButton1.setText(resourceMap.getString("jRadioButton1.text")); // NOI18N
+ jRadioButton1.setName("jRadioButton1"); // NOI18N
+
+ jRadioButton2.setText(resourceMap.getString("jRadioButton2.text")); // NOI18N
+ jRadioButton2.setName("jRadioButton2"); // NOI18N
+
+ jRadioButton3.setText(resourceMap.getString("jRadioButton3.text")); // NOI18N
+ jRadioButton3.setName("jRadioButton3"); // NOI18N
+
+ jToggleButton1.setText(resourceMap.getString("jToggleButton1.text")); // NOI18N
+ jToggleButton1.setName("jToggleButton1"); // NOI18N
+
+ jToggleButton2.setText(resourceMap.getString("jToggleButton2.text")); // NOI18N
+ jToggleButton2.setName("jToggleButton2"); // NOI18N
+
+ jToggleButton3.setText(resourceMap.getString("jToggleButton3.text")); // NOI18N
+ jToggleButton3.setName("jToggleButton3"); // NOI18N
+
+ javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+ jPanel3.setLayout(jPanel3Layout);
+ jPanel3Layout.setHorizontalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addGap(20, 20, 20)
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jLabel8)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addComponent(jToggleButton1)
+ .addGap(18, 18, 18)
+ .addComponent(jToggleButton2))))
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addGap(40, 40, 40)
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jRadioButton2)
+ .addComponent(jRadioButton1)
+ .addComponent(jRadioButton3))))
+ .addGap(9, 9, 9))
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup()
+ .addContainerGap(339, Short.MAX_VALUE)
+ .addComponent(jToggleButton3)
+ .addContainerGap())
+ );
+ jPanel3Layout.setVerticalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel8)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(jRadioButton1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jRadioButton2)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jRadioButton3)
+ .addGap(18, 18, 18)
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jToggleButton1)
+ .addComponent(jToggleButton2))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 181, Short.MAX_VALUE)
+ .addComponent(jToggleButton3)
+ .addContainerGap())
+ );
+
+ jTabbedPane1.addTab(resourceMap.getString("jPanel3.TabConstraints.tabTitle"), jPanel3); // NOI18N
+
+ jPanel4.setName("jPanel4"); // NOI18N
+
+ jScrollPane1.setName("jScrollPane1"); // NOI18N
+
+ jScrollPane2.setName("jScrollPane2"); // NOI18N
+
+ jLabel9.setText(resourceMap.getString("jLabel9.text")); // NOI18N
+ jLabel9.setName("jLabel9"); // NOI18N
+
+ jLabel10.setText(resourceMap.getString("jLabel10.text")); // NOI18N
+ jLabel10.setName("jLabel10"); // NOI18N
+
+ jLabel11.setText(resourceMap.getString("jLabel11.text")); // NOI18N
+ jLabel11.setName("jLabel11"); // NOI18N
+
+ javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
+ jPanel4.setLayout(jPanel4Layout);
+ jPanel4Layout.setHorizontalGroup(
+ jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+ .addComponent(jLabel9, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+ .addComponent(jLabel10)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+ .addComponent(jLabel11))
+ .addContainerGap())
+ );
+ jPanel4Layout.setVerticalGroup(
+ jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel10)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jLabel11)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 122, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap())
+ );
+
+ jTabbedPane1.addTab(resourceMap.getString("jPanel4.TabConstraints.tabTitle"), jPanel4); // NOI18N
+
+ jPanel5.setName("jPanel5"); // NOI18N
+
+ javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5);
+ jPanel5.setLayout(jPanel5Layout);
+ jPanel5Layout.setHorizontalGroup(
+ jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 562, Short.MAX_VALUE)
+ );
+ jPanel5Layout.setVerticalGroup(
+ jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 388, Short.MAX_VALUE)
+ );
+
+ jTabbedPane1.addTab(resourceMap.getString("jPanel5.TabConstraints.tabTitle"), jPanel5); // NOI18N
+
+ jPanel6.setName("jPanel6"); // NOI18N
+
+ javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6);
+ jPanel6.setLayout(jPanel6Layout);
+ jPanel6Layout.setHorizontalGroup(
+ jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 562, Short.MAX_VALUE)
+ );
+ jPanel6Layout.setVerticalGroup(
+ jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 388, Short.MAX_VALUE)
+ );
+
+ jTabbedPane1.addTab(resourceMap.getString("jPanel6.TabConstraints.tabTitle"), jPanel6); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(12, 12, 12)
+ .addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 566, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addComponent(jTabbedPane1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JToggleButton cancel;
+ private javax.swing.JComboBox jComboBox1;
+ private javax.swing.JComboBox jComboBox2;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel10;
+ private javax.swing.JLabel jLabel11;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ private javax.swing.JLabel jLabel4;
+ private javax.swing.JLabel jLabel5;
+ private javax.swing.JLabel jLabel6;
+ private javax.swing.JLabel jLabel7;
+ private javax.swing.JLabel jLabel8;
+ private javax.swing.JLabel jLabel9;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel jPanel3;
+ private javax.swing.JPanel jPanel4;
+ private javax.swing.JPanel jPanel5;
+ private javax.swing.JPanel jPanel6;
+ private javax.swing.JRadioButton jRadioButton1;
+ private javax.swing.JRadioButton jRadioButton2;
+ private javax.swing.JRadioButton jRadioButton3;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JScrollPane jScrollPane2;
+ private javax.swing.JTabbedPane jTabbedPane1;
+ private javax.swing.JTextField jTextField1;
+ private javax.swing.JTextField jTextField2;
+ private javax.swing.JTextField jTextField3;
+ private javax.swing.JTextField jTextField4;
+ private javax.swing.JToggleButton jToggleButton1;
+ private javax.swing.JToggleButton jToggleButton2;
+ private javax.swing.JToggleButton jToggleButton3;
+ private javax.swing.JToggleButton ok;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/apps/desktopgui/src/gui/JPopupTrayIcon.java b/apps/desktopgui/src/gui/JPopupTrayIcon.java
new file mode 100644
index 0000000000..83872e4a51
--- /dev/null
+++ b/apps/desktopgui/src/gui/JPopupTrayIcon.java
@@ -0,0 +1,176 @@
+/*
+* Created on Sep 15, 2008 5:51:33 PM
+*/
+
+/*
+ * This class is part of fishfarm project: https://fishfarm.dev.java.net/
+ * It is licensed under the GPL version 2.0 with Classpath Exception.
+ *
+ * Copyright (C) 2008 Michael Bien
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+package gui;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.PopupMenu;
+import java.awt.TrayIcon;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JWindow;
+import javax.swing.RootPaneContainer;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+
+
+/**
+ * JPopupMenu compatible TrayIcon based on Alexander Potochkin's JXTrayIcon
+ * (http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html)
+ * but uses a JWindow instead of a JDialog to workaround some bugs on linux.
+ *
+ * @author Michael Bien
+ */
+public class JPopupTrayIcon extends TrayIcon {
+
+ private JPopupMenu menu;
+
+ private Window window;
+ private PopupMenuListener popupListener;
+
+ private final static boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows");
+
+ public JPopupTrayIcon(Image image) {
+ super(image);
+ init();
+ }
+
+ public JPopupTrayIcon(Image image, String tooltip) {
+ super(image, tooltip);
+ init();
+ }
+
+ public JPopupTrayIcon(Image image, String tooltip, PopupMenu popup) {
+ super(image, tooltip, popup);
+ init();
+ }
+
+ public JPopupTrayIcon(Image image, String tooltip, JPopupMenu popup) {
+ super(image, tooltip);
+ init();
+ setJPopupMenu(popup);
+ }
+
+
+ private final void init() {
+
+
+ popupListener = new PopupMenuListener() {
+
+ @Override
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+// System.out.println("popupMenuWillBecomeVisible");
+ }
+
+ @Override
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+// System.out.println("popupMenuWillBecomeInvisible");
+ if(window != null) {
+ window.dispose();
+ window = null;
+ }
+ }
+
+ @Override
+ public void popupMenuCanceled(PopupMenuEvent e) {
+// System.out.println("popupMenuCanceled");
+ if(window != null) {
+ window.dispose();
+ window = null;
+ }
+ }
+ };
+
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+// System.out.println(e.getPoint());
+ showJPopupMenu(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+// System.out.println(e.getPoint());
+ showJPopupMenu(e);
+ }
+ });
+
+ }
+
+ private final void showJPopupMenu(MouseEvent e) {
+ if(e.isPopupTrigger() && menu != null) {
+ if (window == null) {
+
+ if(IS_WINDOWS) {
+ window = new JDialog((Frame)null);
+ ((JDialog)window).setUndecorated(true);
+ }else{
+ window = new JWindow((Frame)null);
+ }
+ window.setAlwaysOnTop(true);
+ Dimension size = menu.getPreferredSize();
+
+ Point centerPoint = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
+ if(e.getY() > centerPoint.getY())
+ window.setLocation(e.getX(), e.getY() - size.height);
+ else
+ window.setLocation(e.getX(), e.getY());
+
+ window.setVisible(true);
+
+ menu.show(((RootPaneContainer)window).getContentPane(), 0, 0);
+
+ // popup works only for focused windows
+ window.toFront();
+
+ }
+ }
+ }
+
+
+ public final JPopupMenu getJPopupMenu() {
+ return menu;
+ }
+
+ public final void setJPopupMenu(JPopupMenu menu) {
+ if (this.menu != null) {
+ this.menu.removePopupMenuListener(popupListener);
+ }
+ this.menu = menu;
+ menu.addPopupMenuListener(popupListener);
+ }
+
+}
diff --git a/apps/desktopgui/src/gui/LogViewer.form b/apps/desktopgui/src/gui/LogViewer.form
new file mode 100644
index 0000000000..b53b410bac
--- /dev/null
+++ b/apps/desktopgui/src/gui/LogViewer.form
@@ -0,0 +1,101 @@
+
+
+
diff --git a/apps/desktopgui/src/gui/LogViewer.java b/apps/desktopgui/src/gui/LogViewer.java
new file mode 100644
index 0000000000..2dad70c1fc
--- /dev/null
+++ b/apps/desktopgui/src/gui/LogViewer.java
@@ -0,0 +1,163 @@
+/*
+ * LogViewer.java
+ *
+ * Created on 10 april 2009, 19:17
+ */
+
+package gui;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.WindowConstants;
+
+/**
+ *
+ * @author mathias
+ */
+public class LogViewer extends javax.swing.JFrame {
+
+ /** Creates new form LogViewer */
+ public LogViewer() {
+ initComponents();
+ readLogText();
+ this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ this.setVisible(true);
+ }
+
+ private void readLogText() {
+ Thread t = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String s = "";
+ File f = new File(LOGLOCATION);
+ if(f.exists()) {
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(f));
+ while(true) {
+ String line = br.readLine();
+ if(line != null)
+ s += JTEXTNEWLINE + line;
+ else
+ break;
+ }
+ }
+ catch(Exception e) {
+ s = "An error has occurred while loading the logfiles:" + JTEXTNEWLINE + e.getMessage();
+ }
+ }
+ logText.setText(s);
+ }
+
+ });
+ t.start();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ textScroll = new javax.swing.JScrollPane();
+ logText = new javax.swing.JTextArea();
+ explanationText = new javax.swing.JLabel();
+ refreshButton = new javax.swing.JButton();
+ clearButton = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ setName("Form"); // NOI18N
+
+ textScroll.setName("textScroll"); // NOI18N
+
+ logText.setColumns(20);
+ logText.setRows(5);
+ logText.setName("logText"); // NOI18N
+ textScroll.setViewportView(logText);
+
+ org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(LogViewer.class);
+ explanationText.setText(resourceMap.getString("explanationText.text")); // NOI18N
+ explanationText.setName("explanationText"); // NOI18N
+
+ refreshButton.setText(resourceMap.getString("refreshButton.text")); // NOI18N
+ refreshButton.setName("refreshButton"); // NOI18N
+ refreshButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ refreshButtonActionPerformed(evt);
+ }
+ });
+
+ clearButton.setText(resourceMap.getString("clearButton.text")); // NOI18N
+ clearButton.setName("clearButton"); // NOI18N
+ clearButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ clearButtonActionPerformed(evt);
+ }
+ });
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(12, 12, 12)
+ .addComponent(explanationText, javax.swing.GroupLayout.PREFERRED_SIZE, 561, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap())
+ .addComponent(textScroll, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 722, Short.MAX_VALUE)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(refreshButton)
+ .addGap(18, 18, 18)
+ .addComponent(clearButton)
+ .addContainerGap(587, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(explanationText, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(refreshButton)
+ .addComponent(clearButton))
+ .addGap(14, 14, 14)
+ .addComponent(textScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 330, Short.MAX_VALUE))
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+private void clearButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearButtonActionPerformed
+ File f = new File(LOGLOCATION);
+ f.delete();
+ try {
+ f.createNewFile();//GEN-LAST:event_clearButtonActionPerformed
+ } catch (IOException ex) {
+ Logger.getLogger(LogViewer.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ readLogText();
+}
+
+private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
+ readLogText();
+}//GEN-LAST:event_refreshButtonActionPerformed
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton clearButton;
+ private javax.swing.JLabel explanationText;
+ private javax.swing.JTextArea logText;
+ private javax.swing.JButton refreshButton;
+ private javax.swing.JScrollPane textScroll;
+ // End of variables declaration//GEN-END:variables
+
+ private static final String LOGLOCATION = "wrapper.log";
+ private static final String JTEXTNEWLINE = "\n";
+}
diff --git a/apps/desktopgui/src/gui/SpeedSelector.form b/apps/desktopgui/src/gui/SpeedSelector.form
new file mode 100644
index 0000000000..79d25ae965
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelector.form
@@ -0,0 +1,178 @@
+
+
+
diff --git a/apps/desktopgui/src/gui/SpeedSelector.java b/apps/desktopgui/src/gui/SpeedSelector.java
new file mode 100644
index 0000000000..bce18621f2
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelector.java
@@ -0,0 +1,195 @@
+/*
+ * ProfileSelector.java
+ *
+ * Created on 3 april 2009, 13:57
+ */
+
+package gui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.Properties;
+import javax.swing.JComboBox;
+import javax.swing.JTextField;
+import persistence.PropertyManager;
+import util.IntegerVerifier;
+
+/**
+ *
+ * @author mathias
+ */
+public class SpeedSelector extends javax.swing.JFrame {
+
+ /** Creates new form ProfileSelector */
+ public SpeedSelector() {
+ this.props = PropertyManager.getProps();
+ initComponents();
+ initComponentsCustom();
+ initSpeeds(props);
+ this.setVisible(true);
+ this.setLocationRelativeTo(null);
+ this.requestFocus();
+ }
+
+ public SpeedSelector(Point point) {
+ this();
+ this.setLocation(point);
+ }
+
+ public void initComponentsCustom() {
+ ((JTextField)uploadChoice.getEditor().getEditorComponent()).setInputVerifier(new IntegerVerifier());
+ ((JTextField)downloadChoice.getEditor().getEditorComponent()).setInputVerifier(new IntegerVerifier());
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ nextButton = new javax.swing.JButton();
+ uploadLabel = new javax.swing.JLabel();
+ downloadLabel = new javax.swing.JLabel();
+ uploadChoice = new javax.swing.JComboBox();
+ downloadChoice = new javax.swing.JComboBox();
+ speedExplanation = new javax.swing.JLabel();
+ uploadkbps = new javax.swing.JComboBox();
+ downloadkbps = new javax.swing.JComboBox();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector.class);
+ setTitle(resourceMap.getString("Form.title")); // NOI18N
+ setMinimumSize(new java.awt.Dimension(610, 330));
+ setName("Form"); // NOI18N
+ setResizable(false);
+ getContentPane().setLayout(null);
+
+ nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N
+ nextButton.setMaximumSize(new java.awt.Dimension(72, 29));
+ nextButton.setMinimumSize(new java.awt.Dimension(72, 29));
+ nextButton.setName("nextButton"); // NOI18N
+ nextButton.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ nextButtonMouseClicked(evt);
+ }
+ });
+ getContentPane().add(nextButton);
+ nextButton.setBounds(440, 250, 90, 29);
+
+ uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N
+ uploadLabel.setName("uploadLabel"); // NOI18N
+ getContentPane().add(uploadLabel);
+ uploadLabel.setBounds(20, 60, 246, 30);
+
+ downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N
+ downloadLabel.setName("downloadLabel"); // NOI18N
+ getContentPane().add(downloadLabel);
+ downloadLabel.setBounds(20, 110, 263, 30);
+
+ uploadChoice.setEditable(true);
+ uploadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" }));
+ uploadChoice.setSelectedIndex(3);
+ uploadChoice.setName("uploadChoice"); // NOI18N
+ getContentPane().add(uploadChoice);
+ uploadChoice.setBounds(300, 60, 154, 27);
+
+ downloadChoice.setEditable(true);
+ downloadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" }));
+ downloadChoice.setSelectedIndex(3);
+ downloadChoice.setName("downloadChoice"); // NOI18N
+ getContentPane().add(downloadChoice);
+ downloadChoice.setBounds(300, 110, 154, 27);
+
+ speedExplanation.setText(resourceMap.getString("speedExplanation.text")); // NOI18N
+ speedExplanation.setName("speedExplanation"); // NOI18N
+ getContentPane().add(speedExplanation);
+ speedExplanation.setBounds(20, 160, 570, 60);
+
+ uploadkbps.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+ uploadkbps.setName("uploadKbit"); // NOI18N
+ uploadkbps.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ uploadkbpsActionPerformed(evt);
+ }
+ });
+ getContentPane().add(uploadkbps);
+ uploadkbps.setBounds(470, 60, 68, 27);
+
+ downloadkbps.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+ downloadkbps.setName("downloadKbit"); // NOI18N
+ downloadkbps.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ downloadkbpsActionPerformed(evt);
+ }
+ });
+ getContentPane().add(downloadkbps);
+ downloadkbps.setBounds(470, 110, 68, 27);
+
+ pack();
+ }// //GEN-END:initComponents
+
+private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked
+ if(uploadkbps.getSelectedIndex() == KILOBIT)
+ props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, uploadChoice.getSelectedItem().toString());
+ else
+ props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, "" + Integer.parseInt(uploadChoice.getSelectedItem().toString())*8);
+ if(downloadkbps.getSelectedIndex() == KILOBIT)
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, downloadChoice.getSelectedItem().toString());
+ else
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, "" + Integer.parseInt(downloadChoice.getSelectedItem().toString())*8);
+ PropertyManager.saveProps(props);
+ new SpeedSelector2(this.getLocationOnScreen());
+ this.dispose();
+}//GEN-LAST:event_nextButtonMouseClicked
+
+private void uploadkbpsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uploadkbpsActionPerformed
+ kbpsSwitchPerformed(uploadkbps, uploadChoice);
+}//GEN-LAST:event_uploadkbpsActionPerformed
+
+private void downloadkbpsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadkbpsActionPerformed
+ kbpsSwitchPerformed(downloadkbps, downloadChoice);
+}//GEN-LAST:event_downloadkbpsActionPerformed
+
+private void kbpsSwitchPerformed(JComboBox kbps, JComboBox speed) {
+ int index = kbps.getSelectedIndex();
+ int previous = Integer.parseInt(speed.getSelectedItem().toString());
+ if(index == KILOBIT) {
+ speed.setSelectedItem("" + previous*8);
+ }
+ else {
+ speed.setSelectedItem("" + previous/8);
+ }
+}
+
+private void initSpeeds(Properties props) {
+ String up = props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE);
+ String down = props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE);
+
+ if(up == null)
+ props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, "1000");
+ if(down == null)
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, "1000");
+
+ uploadChoice.setSelectedItem(props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE));
+ downloadChoice.setSelectedItem(props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE));
+}
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JComboBox downloadChoice;
+ private javax.swing.JLabel downloadLabel;
+ private javax.swing.JComboBox downloadkbps;
+ private javax.swing.JButton nextButton;
+ private javax.swing.JLabel speedExplanation;
+ private javax.swing.JComboBox uploadChoice;
+ private javax.swing.JLabel uploadLabel;
+ private javax.swing.JComboBox uploadkbps;
+ // End of variables declaration//GEN-END:variables
+
+ Properties props;
+ private static final int KILOBIT = 0;
+ private static final int KILOBYTE = 1;
+}
diff --git a/apps/desktopgui/src/gui/SpeedSelector2.form b/apps/desktopgui/src/gui/SpeedSelector2.form
new file mode 100644
index 0000000000..5c305de19b
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelector2.form
@@ -0,0 +1,117 @@
+
+
+
diff --git a/apps/desktopgui/src/gui/SpeedSelector2.java b/apps/desktopgui/src/gui/SpeedSelector2.java
new file mode 100644
index 0000000000..1177b57258
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelector2.java
@@ -0,0 +1,160 @@
+/*
+ * ProfileSelector2.java
+ *
+ * Created on 3 april 2009, 14:36
+ */
+
+package gui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.Enumeration;
+import java.util.Properties;
+import javax.swing.AbstractButton;
+import persistence.PropertyManager;
+
+/**
+ *
+ * @author mathias
+ */
+public class SpeedSelector2 extends javax.swing.JFrame {
+ Properties props;
+
+ /** Creates new form ProfileSelector2 */
+ public SpeedSelector2(Point point) {
+ this.props = PropertyManager.getProps();
+ initComponents();
+ this.setLocation(point);
+ loadButtonSelection();
+ this.setVisible(true);
+ this.requestFocus();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ buttonGroup1 = new javax.swing.ButtonGroup();
+ nextButton = new javax.swing.JButton();
+ returnButton = new javax.swing.JButton();
+ questionLabel = new javax.swing.JLabel();
+ browseButton = new javax.swing.JRadioButton();
+ downloadButton = new javax.swing.JRadioButton();
+ jLabel1 = new javax.swing.JLabel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector2.class);
+ setTitle(resourceMap.getString("Form.title")); // NOI18N
+ setMinimumSize(new java.awt.Dimension(610, 330));
+ setName("Form"); // NOI18N
+ setResizable(false);
+ getContentPane().setLayout(null);
+
+ nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N
+ nextButton.setName("nextButton"); // NOI18N
+ nextButton.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ nextButtonMouseClicked(evt);
+ }
+ });
+ getContentPane().add(nextButton);
+ nextButton.setBounds(440, 250, 90, 29);
+
+ returnButton.setText(resourceMap.getString("returnButton.text")); // NOI18N
+ returnButton.setName("returnButton"); // NOI18N
+ returnButton.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ returnButtonMouseClicked(evt);
+ }
+ });
+ getContentPane().add(returnButton);
+ returnButton.setBounds(336, 250, 90, 29);
+
+ questionLabel.setText(resourceMap.getString("questionLabel.text")); // NOI18N
+ questionLabel.setName("questionLabel"); // NOI18N
+ getContentPane().add(questionLabel);
+ questionLabel.setBounds(30, 40, 265, 17);
+
+ buttonGroup1.add(browseButton);
+ browseButton.setText(resourceMap.getString("browseButton.text")); // NOI18N
+ browseButton.setActionCommand(resourceMap.getString("browseButton.actionCommand")); // NOI18N
+ browseButton.setName("browseButton"); // NOI18N
+ getContentPane().add(browseButton);
+ browseButton.setBounds(40, 120, 520, 40);
+
+ buttonGroup1.add(downloadButton);
+ downloadButton.setText(resourceMap.getString("downloadButton.text")); // NOI18N
+ downloadButton.setActionCommand(resourceMap.getString("downloadButton.actionCommand")); // NOI18N
+ downloadButton.setName("downloadButton"); // NOI18N
+ getContentPane().add(downloadButton);
+ downloadButton.setBounds(40, 70, 499, 40);
+
+ jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
+ jLabel1.setName("jLabel1"); // NOI18N
+ getContentPane().add(jLabel1);
+ jLabel1.setBounds(30, 170, 530, 70);
+
+ pack();
+ }// //GEN-END:initComponents
+
+private void returnButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_returnButtonMouseClicked
+ saveButtonSelection();
+ PropertyManager.saveProps(props);
+ new SpeedSelector(this.getLocationOnScreen());
+ this.dispose();
+}//GEN-LAST:event_returnButtonMouseClicked
+
+private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked
+ saveButtonSelection();
+ PropertyManager.saveProps(props);
+ new SpeedSelector3(this.getLocationOnScreen(), this.getSize());
+ this.dispose();
+}//GEN-LAST:event_nextButtonMouseClicked
+
+private void loadButtonSelection() {
+
+ Enumeration elements = buttonGroup1.getElements();
+ while(elements.hasMoreElements()) {
+ AbstractButton button = elements.nextElement();
+ if(button == null)
+ continue;
+ if(props.getProperty(SpeedSelectorConstants.USERTYPE) == null)
+ break;
+ String type = button.getActionCommand();
+ if(type.equals(props.getProperty(SpeedSelectorConstants.USERTYPE))) {
+ button.setSelected(true);
+ break;
+ }
+ }
+}
+
+private void saveButtonSelection() {
+ Enumeration elements = buttonGroup1.getElements();
+ while(elements.hasMoreElements()) {
+ AbstractButton button = elements.nextElement();
+ if(button == null)
+ continue;
+ if(button.isSelected()) {
+ String type = button.getActionCommand();
+ props.setProperty(SpeedSelectorConstants.USERTYPE, type);
+ break;
+ }
+ }
+}
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JRadioButton browseButton;
+ private javax.swing.ButtonGroup buttonGroup1;
+ private javax.swing.JRadioButton downloadButton;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JButton nextButton;
+ private javax.swing.JLabel questionLabel;
+ private javax.swing.JButton returnButton;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/apps/desktopgui/src/gui/SpeedSelector3.form b/apps/desktopgui/src/gui/SpeedSelector3.form
new file mode 100644
index 0000000000..b295c45938
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelector3.form
@@ -0,0 +1,337 @@
+
+
+
diff --git a/apps/desktopgui/src/gui/SpeedSelector3.java b/apps/desktopgui/src/gui/SpeedSelector3.java
new file mode 100644
index 0000000000..8c6407f629
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelector3.java
@@ -0,0 +1,439 @@
+/*
+ * ProfileSelector3.java
+ *
+ * Created on 3 april 2009, 15:17
+ */
+
+package gui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.Properties;
+import javax.swing.JComboBox;
+import javax.swing.JTextField;
+import persistence.PropertyManager;
+import router.configuration.SpeedHandler;
+import router.configuration.SpeedHelper;
+
+/**
+ *
+ * @author mathias
+ */
+public class SpeedSelector3 extends javax.swing.JFrame {
+ Properties props;
+
+ /** Creates new form ProfileSelector3 */
+ public SpeedSelector3(Point point, Dimension dimension) {
+ this.props = PropertyManager.getProps();
+ initComponents();
+ this.setLocation(point);
+ this.setSize(dimension);
+ initSpeeds();
+ initUsage();
+ this.setVisible(true);
+ this.requestFocus();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ finishButton = new javax.swing.JButton();
+ previousButton = new javax.swing.JButton();
+ settingsInfo = new javax.swing.JLabel();
+ uploadLabel = new javax.swing.JLabel();
+ downloadLabel = new javax.swing.JLabel();
+ uploadBurstLabel = new javax.swing.JLabel();
+ downloadBurstLabel = new javax.swing.JLabel();
+ uploadUsageLabel = new javax.swing.JLabel();
+ downloadUsageLabel = new javax.swing.JLabel();
+ uploadField = new javax.swing.JTextField();
+ uploadBurstField = new javax.swing.JTextField();
+ downloadField = new javax.swing.JTextField();
+ downloadBurstField = new javax.swing.JTextField();
+ kbpsBurstDownload = new javax.swing.JComboBox();
+ kbpsUpload = new javax.swing.JComboBox();
+ kbpsBurstUpload = new javax.swing.JComboBox();
+ kbpsDownload = new javax.swing.JComboBox();
+ uploadGB = new javax.swing.JLabel();
+ uploadMonth = new javax.swing.JTextField();
+ downloadMonth = new javax.swing.JTextField();
+ downloadGB = new javax.swing.JLabel();
+ explanation = new javax.swing.JLabel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector3.class);
+ setTitle(resourceMap.getString("Form.title")); // NOI18N
+ setMinimumSize(new java.awt.Dimension(670, 330));
+ setName("Form"); // NOI18N
+ setResizable(false);
+ getContentPane().setLayout(null);
+
+ finishButton.setText(resourceMap.getString("finishButton.text")); // NOI18N
+ finishButton.setName("finishButton"); // NOI18N
+ finishButton.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ finishButtonMouseClicked(evt);
+ }
+ });
+ getContentPane().add(finishButton);
+ finishButton.setBounds(440, 250, 90, 29);
+
+ previousButton.setText(resourceMap.getString("previousButton.text")); // NOI18N
+ previousButton.setName("previousButton"); // NOI18N
+ previousButton.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ previousButtonMouseClicked(evt);
+ }
+ });
+ getContentPane().add(previousButton);
+ previousButton.setBounds(336, 250, 90, 29);
+
+ settingsInfo.setText(resourceMap.getString("settingsInfo.text")); // NOI18N
+ settingsInfo.setName("settingsInfo"); // NOI18N
+ getContentPane().add(settingsInfo);
+ settingsInfo.setBounds(20, 30, 532, 17);
+
+ uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N
+ uploadLabel.setName("uploadLabel"); // NOI18N
+ getContentPane().add(uploadLabel);
+ uploadLabel.setBounds(20, 70, 140, 30);
+
+ downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N
+ downloadLabel.setName("downloadLabel"); // NOI18N
+ getContentPane().add(downloadLabel);
+ downloadLabel.setBounds(340, 70, 160, 30);
+
+ uploadBurstLabel.setText(resourceMap.getString("uploadBurstLabel.text")); // NOI18N
+ uploadBurstLabel.setName("uploadBurstLabel"); // NOI18N
+ getContentPane().add(uploadBurstLabel);
+ uploadBurstLabel.setBounds(20, 110, 140, 30);
+
+ downloadBurstLabel.setText(resourceMap.getString("downloadBurstLabel.text")); // NOI18N
+ downloadBurstLabel.setName("downloadBurstLabel"); // NOI18N
+ getContentPane().add(downloadBurstLabel);
+ downloadBurstLabel.setBounds(340, 110, 160, 30);
+
+ uploadUsageLabel.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N
+ uploadUsageLabel.setName("uploadUsageLabel"); // NOI18N
+ getContentPane().add(uploadUsageLabel);
+ uploadUsageLabel.setBounds(20, 150, 140, 30);
+
+ downloadUsageLabel.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N
+ downloadUsageLabel.setName("downloadUsageLabel"); // NOI18N
+ getContentPane().add(downloadUsageLabel);
+ downloadUsageLabel.setBounds(340, 150, 160, 30);
+
+ uploadField.setText(resourceMap.getString("uploadField.text")); // NOI18N
+ uploadField.setMinimumSize(new java.awt.Dimension(77, 27));
+ uploadField.setName("uploadField"); // NOI18N
+ uploadField.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyReleased(java.awt.event.KeyEvent evt) {
+ speedFieldKeyReleased(evt);
+ }
+ });
+ getContentPane().add(uploadField);
+ uploadField.setBounds(160, 70, 77, 27);
+
+ uploadBurstField.setText(resourceMap.getString("uploadBurstField.text")); // NOI18N
+ uploadBurstField.setMinimumSize(new java.awt.Dimension(77, 27));
+ uploadBurstField.setName("uploadBurstField"); // NOI18N
+ getContentPane().add(uploadBurstField);
+ uploadBurstField.setBounds(160, 110, 77, 27);
+
+ downloadField.setText(resourceMap.getString("downloadField.text")); // NOI18N
+ downloadField.setMinimumSize(new java.awt.Dimension(77, 27));
+ downloadField.setName("downloadField"); // NOI18N
+ downloadField.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyReleased(java.awt.event.KeyEvent evt) {
+ speedFieldKeyReleased(evt);
+ }
+ });
+ getContentPane().add(downloadField);
+ downloadField.setBounds(500, 70, 77, 27);
+
+ downloadBurstField.setText(resourceMap.getString("downloadBurstField.text")); // NOI18N
+ downloadBurstField.setMinimumSize(new java.awt.Dimension(77, 27));
+ downloadBurstField.setName("downloadBurstField"); // NOI18N
+ getContentPane().add(downloadBurstField);
+ downloadBurstField.setBounds(500, 110, 77, 27);
+
+ kbpsBurstDownload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+ kbpsBurstDownload.setName("kbpsBurstDownload"); // NOI18N
+ kbpsBurstDownload.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ kbpsBurstDownloadActionPerformed(evt);
+ }
+ });
+ getContentPane().add(kbpsBurstDownload);
+ kbpsBurstDownload.setBounds(580, 110, 68, 27);
+
+ kbpsUpload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+ kbpsUpload.setName("kbpsUpload"); // NOI18N
+ kbpsUpload.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ kbpsUploadActionPerformed(evt);
+ }
+ });
+ getContentPane().add(kbpsUpload);
+ kbpsUpload.setBounds(240, 70, 68, 27);
+
+ kbpsBurstUpload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+ kbpsBurstUpload.setName("kbpsBurstUpload"); // NOI18N
+ kbpsBurstUpload.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ kbpsBurstUploadActionPerformed(evt);
+ }
+ });
+ getContentPane().add(kbpsBurstUpload);
+ kbpsBurstUpload.setBounds(240, 110, 68, 27);
+
+ kbpsDownload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+ kbpsDownload.setName("kbpsDownload"); // NOI18N
+ kbpsDownload.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ kbpsDownloadActionPerformed(evt);
+ }
+ });
+ getContentPane().add(kbpsDownload);
+ kbpsDownload.setBounds(580, 70, 68, 27);
+
+ uploadGB.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N
+ uploadGB.setName("uploadUsageLabel"); // NOI18N
+ getContentPane().add(uploadGB);
+ uploadGB.setBounds(240, 150, 45, 30);
+
+ uploadMonth.setText(resourceMap.getString("uploadMonth.text")); // NOI18N
+ uploadMonth.setName("uploadMonth"); // NOI18N
+ uploadMonth.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyReleased(java.awt.event.KeyEvent evt) {
+ monthKeyReleased(evt);
+ }
+ });
+ getContentPane().add(uploadMonth);
+ uploadMonth.setBounds(160, 150, 77, 27);
+
+ downloadMonth.setText(resourceMap.getString("downloadMonth.text")); // NOI18N
+ downloadMonth.setName("downloadMonth"); // NOI18N
+ downloadMonth.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyReleased(java.awt.event.KeyEvent evt) {
+ monthKeyReleased(evt);
+ }
+ });
+ getContentPane().add(downloadMonth);
+ downloadMonth.setBounds(500, 150, 77, 27);
+
+ downloadGB.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N
+ downloadGB.setName("downloadUsageLabel"); // NOI18N
+ getContentPane().add(downloadGB);
+ downloadGB.setBounds(580, 150, 19, 30);
+
+ explanation.setText(resourceMap.getString("explanation.text")); // NOI18N
+ explanation.setName("explanation"); // NOI18N
+ getContentPane().add(explanation);
+ explanation.setBounds(20, 180, 600, 70);
+
+ pack();
+ }// //GEN-END:initComponents
+
+private void previousButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_previousButtonMouseClicked
+ saveSpeeds();
+ PropertyManager.saveProps(props);
+ new SpeedSelector2(this.getLocationOnScreen());
+ this.dispose();
+}//GEN-LAST:event_previousButtonMouseClicked
+
+private void finishButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_finishButtonMouseClicked
+ saveSpeeds();
+ PropertyManager.saveProps(props);
+
+ int maxDownload = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXDOWNLOAD));
+ int maxUpload = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXUPLOAD));
+ int maxUploadBurst = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXUPLOADBURST));
+ int maxDownloadBurst = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXDOWNLOADBURST));
+
+ //Working in kB, not kb!
+ SpeedHandler.setInboundBandwidth(maxDownload/8);
+ SpeedHandler.setOutboundBandwidth(maxUpload/8);
+ SpeedHandler.setInboundBurstBandwidth(maxDownloadBurst/8);
+ SpeedHandler.setOutboundBurstBandwidth(maxUploadBurst/8);
+
+ this.dispose();
+}//GEN-LAST:event_finishButtonMouseClicked
+
+private void speedFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_speedFieldKeyReleased
+ try {
+ String upload = "";
+ if(kbpsUpload.getSelectedIndex() == KILOBIT)
+ upload = uploadField.getText();
+ else
+ upload = "" + Integer.parseInt(uploadField.getText())*8;
+ String download = "";
+ if(kbpsDownload.getSelectedIndex() == KILOBIT)
+ download = downloadField.getText();
+ else
+ download = "" + Integer.parseInt(downloadField.getText())*8;
+ initUsage(upload, download);
+ }
+ catch(NumberFormatException e) {
+ return;
+ }
+}//GEN-LAST:event_speedFieldKeyReleased
+
+private void kbpsUploadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsUploadActionPerformed
+ kbpsSwitchPerformed(kbpsUpload, uploadField);
+}//GEN-LAST:event_kbpsUploadActionPerformed
+
+private void kbpsBurstUploadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsBurstUploadActionPerformed
+ kbpsSwitchPerformed(kbpsBurstUpload, uploadBurstField);
+}//GEN-LAST:event_kbpsBurstUploadActionPerformed
+
+private void kbpsDownloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsDownloadActionPerformed
+ kbpsSwitchPerformed(kbpsDownload, downloadField);
+}//GEN-LAST:event_kbpsDownloadActionPerformed
+
+private void kbpsBurstDownloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsBurstDownloadActionPerformed
+ kbpsSwitchPerformed(kbpsBurstDownload, downloadBurstField);
+}//GEN-LAST:event_kbpsBurstDownloadActionPerformed
+
+private void monthKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_monthKeyReleased
+ try {
+ int uploadMonthValue = Integer.parseInt(uploadMonth.getText());
+ int downloadMonthValue = Integer.parseInt(downloadMonth.getText());
+
+ String upload = "";
+ String burstUpload = "";
+ String download = "";
+ String burstDownload = "";
+
+ if(kbpsUpload.getSelectedIndex() == KILOBIT)
+ upload = "" + SpeedHelper.calculateSpeed(uploadMonthValue)*8; //kbit
+ else
+ upload = "" + SpeedHelper.calculateSpeed(uploadMonthValue); //kbyte
+
+ if(kbpsBurstUpload.getSelectedIndex() == KILOBIT)
+ burstUpload = "" + SpeedHelper.calculateSpeed(uploadMonthValue)*8; //kbit
+ else
+ burstUpload = "" + SpeedHelper.calculateSpeed(uploadMonthValue); //kbyte
+
+ if(kbpsDownload.getSelectedIndex() == KILOBIT)
+ download = "" + SpeedHelper.calculateSpeed(downloadMonthValue)*8; //kbit
+ else
+ download = "" + SpeedHelper.calculateSpeed(downloadMonthValue); //kbyte
+
+ if(kbpsBurstDownload.getSelectedIndex() == KILOBIT)
+ burstDownload = "" + SpeedHelper.calculateSpeed(downloadMonthValue)*8; //kbit
+ else
+ burstDownload = "" + SpeedHelper.calculateSpeed(downloadMonthValue); //kbyte
+
+ initSpeeds(upload, burstUpload, download, burstDownload);
+ }
+ catch(NumberFormatException e) {
+ e.printStackTrace();
+ return;
+ }
+}//GEN-LAST:event_monthKeyReleased
+
+private void kbpsSwitchPerformed(JComboBox kbps, JTextField speed) {
+ int index = kbps.getSelectedIndex();
+ int previous = Integer.parseInt(speed.getText());
+ if(index == KILOBIT) {
+ speed.setText("" + previous*8);
+ }
+ else {
+ speed.setText("" + previous/8);
+ }
+}
+
+ protected void initSpeeds() {
+ String up = "" + SpeedHelper.calculateSpeed(
+ props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE));
+ String upBurst = "" + SpeedHelper.calculateSpeed(
+ props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE));
+ String down = "" + SpeedHelper.calculateSpeed(
+ props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE));
+ String downBurst = "" + SpeedHelper.calculateSpeed(
+ props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE));
+ String userType = props.getProperty(SpeedSelectorConstants.USERTYPE);
+
+ initSpeeds(up, upBurst, down, downBurst);
+ }
+
+ protected void initSpeeds(String up, String upBurst, String down, String downBurst) {
+ uploadField.setText(up);
+ uploadBurstField.setText(upBurst);
+ downloadField.setText(down);
+ downloadBurstField.setText(downBurst);
+ }
+
+ protected void saveSpeeds() {
+ if(kbpsUpload.getSelectedIndex() == KILOBIT)
+ props.setProperty(SpeedSelectorConstants.MAXUPLOAD, uploadField.getText());
+ else
+ props.setProperty(SpeedSelectorConstants.MAXUPLOAD, "" + Integer.parseInt(uploadField.getText())*8);
+ if(kbpsBurstUpload.getSelectedIndex() == KILOBIT)
+ props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, uploadBurstField.getText());
+ else
+ props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, "" + Integer.parseInt(uploadBurstField.getText())*8);
+ if(kbpsDownload.getSelectedIndex() == KILOBIT)
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, downloadField.getText());
+ else
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, "" + Integer.parseInt(downloadField.getText())*8);
+ if(kbpsBurstDownload.getSelectedIndex() == KILOBIT)
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, downloadBurstField.getText());
+ else
+ props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, "" + Integer.parseInt(downloadBurstField.getText())*8);
+ }
+
+ protected void initUsage(String upload, String download) {
+ uploadMonth.setText("" + SpeedHelper.calculateMonthlyUsage(Integer.parseInt(upload)/8));
+ downloadMonth.setText("" + SpeedHelper.calculateMonthlyUsage(Integer.parseInt(download)/8));
+ }
+
+ protected void initUsage() {
+ String upload = "";
+ if(kbpsUpload.getSelectedIndex() == KILOBIT)
+ upload = uploadField.getText();
+ else
+ upload = "" + Integer.parseInt(uploadField.getText())/8;
+ String download = "";
+ if(kbpsDownload.getSelectedIndex() == KILOBIT)
+ download = downloadField.getText();
+ else
+ download = "" + Integer.parseInt(downloadField.getText())/8;
+ initUsage(upload, download);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField downloadBurstField;
+ private javax.swing.JLabel downloadBurstLabel;
+ private javax.swing.JTextField downloadField;
+ private javax.swing.JLabel downloadGB;
+ private javax.swing.JLabel downloadLabel;
+ private javax.swing.JTextField downloadMonth;
+ private javax.swing.JLabel downloadUsageLabel;
+ private javax.swing.JLabel explanation;
+ private javax.swing.JButton finishButton;
+ private javax.swing.JComboBox kbpsBurstDownload;
+ private javax.swing.JComboBox kbpsBurstUpload;
+ private javax.swing.JComboBox kbpsDownload;
+ private javax.swing.JComboBox kbpsUpload;
+ private javax.swing.JButton previousButton;
+ private javax.swing.JLabel settingsInfo;
+ private javax.swing.JTextField uploadBurstField;
+ private javax.swing.JLabel uploadBurstLabel;
+ private javax.swing.JTextField uploadField;
+ private javax.swing.JLabel uploadGB;
+ private javax.swing.JLabel uploadLabel;
+ private javax.swing.JTextField uploadMonth;
+ private javax.swing.JLabel uploadUsageLabel;
+ // End of variables declaration//GEN-END:variables
+
+ private static final int KILOBIT = 0;
+ private static final int KILOBYTE = 1;
+}
diff --git a/apps/desktopgui/src/gui/SpeedSelectorConstants.java b/apps/desktopgui/src/gui/SpeedSelectorConstants.java
new file mode 100644
index 0000000000..ea5e32427b
--- /dev/null
+++ b/apps/desktopgui/src/gui/SpeedSelectorConstants.java
@@ -0,0 +1,25 @@
+package gui;
+
+/**
+ *
+ * @author mathias
+ */
+public class SpeedSelectorConstants {
+ ///Maximum upload speed for the internet connection
+ public static final String MAXUPLOADCAPABLE = "maxUploadCapable";
+ ///Maximum download speed for the internet connection
+ public static final String MAXDOWNLOADCAPABLE = "maxDownloadCapable";
+
+ //User profile type: what behaviour does this user have while using IP2?
+ public static final String USERTYPE = "userType";
+
+ //Maximum upload speed for I2P
+ public static final String MAXUPLOAD = "maxUpload";
+ //Maximum upload burst speed for I2P
+ public static final String MAXUPLOADBURST = "maxUploadBurst";
+
+ //Maximum download speed for I2P
+ public static final String MAXDOWNLOAD = "maxDownload";
+ //Maximum download burst speed for I2P
+ public static final String MAXDOWNLOADBURST = "maxDownloadBurst";
+}
diff --git a/apps/desktopgui/src/gui/Tray.java b/apps/desktopgui/src/gui/Tray.java
new file mode 100644
index 0000000000..7c95cd6eb3
--- /dev/null
+++ b/apps/desktopgui/src/gui/Tray.java
@@ -0,0 +1,228 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package gui;
+
+import desktopgui.*;
+import java.awt.AWTException;
+import java.awt.Desktop;
+import java.awt.Image;
+import java.awt.SystemTray;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import router.RouterHandler;
+import router.RouterHelper;
+import router.configuration.PeerHelper;
+
+/**
+ *
+ * @author mathias
+ */
+public class Tray {
+
+ public Tray() {
+ tray = SystemTray.getSystemTray();
+ loadSystemTray();
+ }
+
+ private void loadSystemTray() {
+
+ Image image = Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo.jpg");
+
+ final JPopupMenu popup = new JPopupMenu();
+
+ //Create menu items to put in the popup menu
+ JMenuItem browserLauncher = new JMenuItem("Launch browser");
+ browserLauncher.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ if(Desktop.isDesktopSupported()) {
+ Desktop desktop = Desktop.getDesktop();
+ try {
+ if(desktop.isSupported(Desktop.Action.BROWSE)) {
+ desktop.browse(new URI("http://localhost:7657"));
+ }
+ else {
+ trayIcon.displayMessage("Browser not found", "The default browser for your system was not found.", TrayIcon.MessageType.WARNING);
+ }
+ } catch (URISyntaxException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ } catch(IOException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
+ });
+ JMenuItem howto = new JMenuItem("How to use I2P");
+ howto.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ if(Desktop.isDesktopSupported()) {
+ Desktop desktop = Desktop.getDesktop();
+ try {
+ File f = new File("desktopgui/resources/howto/howto.html");
+ System.out.println(new URI(null, null, null, 0, "file://" + f.getAbsolutePath(), null, null));
+ desktop.browse(new URI(null, null, null, 0, "file://" + f.getAbsolutePath(), null, null));
+ //desktop.browse(new URI("file://" + f.getAbsolutePath()));
+ } catch (URISyntaxException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ } catch(IOException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
+ });
+ JMenu config = new JMenu("Configuration");
+ JMenuItem speedConfig = new JMenuItem("Speed");
+ speedConfig.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ (new SpeedSelector()).setVisible(true);
+ }
+
+ });
+ JMenuItem generalConfig = new JMenuItem("General Configuration");
+ generalConfig.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ new GeneralConfiguration();
+ }
+
+ });
+ JMenuItem advancedConfig = new JMenuItem("Advanced Configuration");
+ advancedConfig.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ if(Desktop.isDesktopSupported()) {
+ Desktop desktop = Desktop.getDesktop();
+ try {
+ desktop.browse(new URI("http://localhost:7657/config.jsp"));
+ } catch (URISyntaxException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ } catch(IOException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
+ });
+ JMenuItem viewLog = new JMenuItem("View log");
+ viewLog.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ new LogViewer();
+ }
+
+ });
+ JMenuItem version = new JMenuItem("Version");
+ version.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ new Version();
+ }
+
+ });
+ JMenuItem shutdown = new JMenuItem("Shutdown I2P");
+ shutdown.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ RouterHandler.setStatus(RouterHandler.SHUTDOWN_GRACEFULLY);
+ long shutdownTime = RouterHelper.getGracefulShutdownTimeRemaining();
+ System.out.println(shutdownTime);
+ if(shutdownTime>0) {
+ trayIcon.displayMessage("Shutting down...", "Shutdown time remaining: " + shutdownTime/1000 + " seconds."
+ + System.getProperty("line.separator") + "Shutdown will not happen immediately, because we are still participating in the network.", TrayIcon.MessageType.INFO);
+ }
+ else {
+ trayIcon.displayMessage("Shutting down...", "Shutting down immediately.", TrayIcon.MessageType.INFO);
+ }
+ }
+
+ });
+
+ //Add menu items to popup menu
+ popup.add(browserLauncher);
+ popup.add(howto);
+
+ popup.addSeparator();
+
+ config.add(speedConfig);
+ //config.add(generalConfig);
+ config.add(advancedConfig);
+ popup.add(config);
+
+ popup.addSeparator();
+
+ popup.add(viewLog);
+ popup.add(version);
+
+ popup.addSeparator();
+
+ popup.add(shutdown);
+
+ //Add tray icon
+ trayIcon = new JPopupTrayIcon(image, "I2P: the anonymous network", popup);
+
+ try {
+ tray.add(trayIcon);
+ } catch (AWTException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ PeerHelper.addReachabilityListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ updateTooltip();
+ }
+
+ });
+ PeerHelper.addActivePeerListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ updateTooltip();
+ int activePeers = PeerHelper.getActivePeers();
+ if(activePeers == 0)
+ trayIcon.setImage(Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo_red.jpg"));
+ else if(activePeers < 10)
+ trayIcon.setImage(Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo_orange.jpg"));
+ else
+ trayIcon.setImage(Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo_green.jpg"));
+
+ }
+
+ });
+ }
+
+ public void updateTooltip() {
+ trayIcon.setToolTip("I2P Network status: " + PeerHelper.getReachability() + " / " + "Active Peers: " + PeerHelper.getActivePeers());
+ }
+
+ private SystemTray tray = null;
+ private JPopupTrayIcon trayIcon = null;
+
+}
diff --git a/apps/desktopgui/src/gui/Version.form b/apps/desktopgui/src/gui/Version.form
new file mode 100644
index 0000000000..20df8621a0
--- /dev/null
+++ b/apps/desktopgui/src/gui/Version.form
@@ -0,0 +1,100 @@
+
+
+
diff --git a/apps/desktopgui/src/gui/Version.java b/apps/desktopgui/src/gui/Version.java
new file mode 100644
index 0000000000..3e5a478c59
--- /dev/null
+++ b/apps/desktopgui/src/gui/Version.java
@@ -0,0 +1,123 @@
+/*
+ * Version.java
+ *
+ * Created on 13 april 2009, 13:48
+ */
+
+package gui;
+
+import javax.swing.JFrame;
+import router.RouterHelper;
+
+/**
+ *
+ * @author mathias
+ */
+public class Version extends javax.swing.JDialog {
+
+ public Version() {
+ this(new JFrame(), true);
+ }
+
+
+ private Version(java.awt.Frame parent, boolean modal) {
+ super(parent, modal);
+ initComponents();
+ String i2pVersion = RouterHelper.getVersion();
+ String guiVersion = desktopgui.GUIVersion.VERSION;
+ this.I2PVersion.setText("
" + i2pVersion + "
");
+ this.GUIVersion.setText("
" + guiVersion + "
");
+ this.setVisible(true);
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ okButton = new javax.swing.JButton();
+ I2Plabel = new javax.swing.JLabel();
+ GUILabel = new javax.swing.JLabel();
+ I2PVersion = new javax.swing.JLabel();
+ GUIVersion = new javax.swing.JLabel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setName("Form"); // NOI18N
+
+ org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(Version.class);
+ okButton.setText(resourceMap.getString("okButton.text")); // NOI18N
+ okButton.setName("okButton"); // NOI18N
+ okButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+
+ I2Plabel.setText(resourceMap.getString("I2Plabel.text")); // NOI18N
+ I2Plabel.setName("I2Plabel"); // NOI18N
+
+ GUILabel.setText(resourceMap.getString("GUILabel.text")); // NOI18N
+ GUILabel.setName("GUILabel"); // NOI18N
+
+ I2PVersion.setText(resourceMap.getString("I2PVersion.text")); // NOI18N
+ I2PVersion.setName("I2PVersion"); // NOI18N
+
+ GUIVersion.setText(resourceMap.getString("GUIVersion.text")); // NOI18N
+ GUIVersion.setName("GUIVersion"); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+ .addComponent(I2Plabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(GUILabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addGap(18, 18, 18)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(I2PVersion, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE)
+ .addComponent(GUIVersion, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE))
+ .addContainerGap())
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap(294, Short.MAX_VALUE)
+ .addComponent(okButton)
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(I2Plabel)
+ .addComponent(I2PVersion))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(GUILabel)
+ .addComponent(GUIVersion))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(okButton)
+ .addContainerGap())
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ this.dispose();
+}//GEN-LAST:event_okButtonActionPerformed
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel GUILabel;
+ private javax.swing.JLabel GUIVersion;
+ private javax.swing.JLabel I2PVersion;
+ private javax.swing.JLabel I2Plabel;
+ private javax.swing.JButton okButton;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/apps/desktopgui/src/gui/resources/GeneralConfiguration.properties b/apps/desktopgui/src/gui/resources/GeneralConfiguration.properties
new file mode 100644
index 0000000000..3dc629a971
--- /dev/null
+++ b/apps/desktopgui/src/gui/resources/GeneralConfiguration.properties
@@ -0,0 +1,29 @@
+
+jPanel2.TabConstraints.tabTitle=Speed
+jPanel3.TabConstraints.tabTitle=Updates
+jPanel4.TabConstraints.tabTitle=Tunnels/Services
+jPanel5.TabConstraints.tabTitle=Network
+jPanel6.TabConstraints.tabTitle=Advanced
+cancel.text=Cancel
+ok.text=OK
+jLabel1.text=Upload speed:
+jLabel2.text=Download speed:
+jTextField1.text=jTextField1
+jTextField2.text=jTextField2
+jLabel3.text=Monthly usage:
+jLabel4.text=Monthly usage:
+jTextField3.text=jTextField3
+jTextField4.text=jTextField4
+jLabel5.text=GB
+jLabel6.text=GB
+jLabel7.text=Explanation ...
+jLabel8.text=What is your preferred automatic update setting?
+jRadioButton1.text=Only inform about updates
+jRadioButton2.text=Download and verify update file, do not restart
+jRadioButton3.text=Download, verify and restart
+jToggleButton1.text=Check for updates now
+jToggleButton2.text=Update available: update now
+jToggleButton3.text=Advanced update configuration
+jLabel9.text=Tunnel explanation
+jLabel10.text=Client tunnels:
+jLabel11.text=Server tunnels:
diff --git a/apps/desktopgui/src/gui/resources/LogViewer.properties b/apps/desktopgui/src/gui/resources/LogViewer.properties
new file mode 100644
index 0000000000..8cb0e62091
--- /dev/null
+++ b/apps/desktopgui/src/gui/resources/LogViewer.properties
@@ -0,0 +1,3 @@
+refreshButton.text=Refresh
+clearButton.text=Clear
+explanationText.text=Explanation ...
diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector.properties b/apps/desktopgui/src/gui/resources/SpeedSelector.properties
new file mode 100644
index 0000000000..00eb6c973d
--- /dev/null
+++ b/apps/desktopgui/src/gui/resources/SpeedSelector.properties
@@ -0,0 +1,6 @@
+
+Form.title=I2P Speed Configuration
+nextButton.text=Next
+uploadLabel.text=What is your maximum upload speed?
+downloadLabel.text=What is your maximum download speed?
+speedExplanation.text=The maximum speed is set by your provider. It can be given in kilobit (kbps) or kilobyte (kBps). One kilobyte equals eight kilobit.
diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties
new file mode 100644
index 0000000000..909518ada2
--- /dev/null
+++ b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties
@@ -0,0 +1,9 @@
+returnButton.text=Previous
+Form.title=I2P Speed Configuration
+questionLabel.text=Which of these descriptions fits you best?
+browseButton.text=Browsing: I want to use I2P to browse websites anonymously, no heavy usage.
+downloadButton.text=Downloading: I want to use I2P for downloads and filesharing, heavy usage.
+nextButton.text=Next
+browseButton.actionCommand=Browsing
+downloadButton.actionCommand=Downloading
+jLabel1.text=I2P can be used for many different purposes. Here, we present two possible descriptions. If you use a lot of bandwidth in I2P (for example using downloading), please check the downloading option. If your bandwidth usage is limited, please check the browsing option.
diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties
new file mode 100644
index 0000000000..5b338348cf
--- /dev/null
+++ b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties
@@ -0,0 +1,17 @@
+Form.title=I2P Configuration
+previousButton.text=Previous
+finishButton.text=Finish
+uploadLabel.text=Upload Speed:
+uploadBurstLabel.text=Burst Upload Speed:
+downloadLabel.text=Download Speed:
+downloadBurstLabel.text=Burst Download Speed:
+uploadUsageLabel.text=Monthly usage:
+downloadUsageLabel.text=Monthly usage:
+uploadField.text=jTextField1
+uploadBurstField.text=jTextField2
+downloadField.text=jTextField4
+downloadBurstField.text=jTextField5
+uploadMonth.text=jTextField1
+downloadMonth.text=jTextField2
+settingsInfo.text=The profile information your entered, indicates that these are your optimal settings:
+explanation.text=We give a suggested upload and download speed. If your provider imposes a monthly bandwidth limit (usually given in gigabyte (GB)), please enter a value lower than that limit. If you run I2P only 50% of the time, you can double the bandwidth limit to use the same amount as when you are online 100% of the time.
diff --git a/apps/desktopgui/src/gui/resources/Version.properties b/apps/desktopgui/src/gui/resources/Version.properties
new file mode 100644
index 0000000000..91ee786ad1
--- /dev/null
+++ b/apps/desktopgui/src/gui/resources/Version.properties
@@ -0,0 +1,5 @@
+okButton.text=OK
+I2Plabel.text=
I2P Version:
+GUILabel.text=
GUI Version:
+I2PVersion.text=jLabel3
+GUIVersion.text=jLabel4
diff --git a/apps/desktopgui/src/persistence/PropertyManager.java b/apps/desktopgui/src/persistence/PropertyManager.java
new file mode 100644
index 0000000000..bacbf348ac
--- /dev/null
+++ b/apps/desktopgui/src/persistence/PropertyManager.java
@@ -0,0 +1,72 @@
+package persistence;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author mathias
+ */
+public class PropertyManager {
+
+ public static void setProps(Properties props) {
+ PropertyManager.props = props;
+ }
+
+ public static Properties getProps() {
+ return props;
+ }
+
+ public static Properties loadProps() {
+ Properties defaultProps = new Properties();
+ defaultProps.setProperty("firstLoad", "true");
+
+ // create application properties with default
+ Properties applicationProps = new Properties(defaultProps);
+
+ // now load properties from last invocation
+ FileInputStream in;
+ try {
+ in = new FileInputStream(PROPSLOCATION);
+ applicationProps.load(in);
+ in.close();
+ } catch (FileNotFoundException ex) {
+ //Nothing serious, just means it's being loaded for the first time.
+ } catch(IOException ex) {
+ Logger.getLogger(PropertyManager.class.getName()).log(Level.INFO, null, ex);
+ }
+ props = applicationProps;
+ return applicationProps;
+ }
+
+ public static void saveProps(Properties props) {
+ FileOutputStream out;
+ try {
+ File d = new File(PROPSDIRECTORY);
+ if(!d.exists())
+ d.mkdir();
+ File f = new File(PROPSLOCATION);
+ if(!f.exists())
+ f.createNewFile();
+ out = new FileOutputStream(f);
+ props.store(out, PROPSLOCATION);
+ } catch (FileNotFoundException ex) {
+ Logger.getLogger(PropertyManager.class.getName()).log(Level.SEVERE, null, ex);
+ } catch(IOException ex) {
+ Logger.getLogger(PropertyManager.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ private static Properties props;
+
+ ///Location where we store the Application Properties
+ public static final String PROPSDIRECTORY = "desktopgui";
+ public static final String PROPSFILENAME = "appProperties";
+ public static final String PROPSLOCATION = PROPSDIRECTORY + File.separator + PROPSFILENAME;
+}
diff --git a/apps/desktopgui/src/router/RouterHandler.java b/apps/desktopgui/src/router/RouterHandler.java
new file mode 100644
index 0000000000..0752f877b5
--- /dev/null
+++ b/apps/desktopgui/src/router/RouterHandler.java
@@ -0,0 +1,38 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package router;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.i2p.router.RouterContext;
+
+/**
+ *
+ * @author mathias
+ */
+public class RouterHandler {
+ public static final int SHUTDOWN_GRACEFULLY = 0;
+ public static void setStatus(int status) {
+ if(status == SHUTDOWN_GRACEFULLY) {
+ Thread t = new Thread(new Runnable() {
+
+ public void run() {
+ RouterContext context = RouterHelper.getContext();
+ context.router().shutdownGracefully();
+ while(context.router().getShutdownTimeRemaining()>0)
+ try {
+ Thread.sleep(context.router().getShutdownTimeRemaining());
+ } catch (InterruptedException ex) {
+ Logger.getLogger(RouterHandler.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ System.exit(0);
+ }
+
+ });
+ t.start();
+ }
+ }
+}
diff --git a/apps/desktopgui/src/router/RouterHelper.java b/apps/desktopgui/src/router/RouterHelper.java
new file mode 100644
index 0000000000..49a7c8b29a
--- /dev/null
+++ b/apps/desktopgui/src/router/RouterHelper.java
@@ -0,0 +1,22 @@
+package router;
+
+import net.i2p.router.RouterContext;
+import net.i2p.router.RouterVersion;
+
+/**
+ *
+ * @author mathias
+ */
+public class RouterHelper {
+ public static RouterContext getContext() {
+ return (RouterContext) RouterContext.listContexts().get(0);
+ }
+
+ public static long getGracefulShutdownTimeRemaining() {
+ return RouterHelper.getContext().router().getShutdownTimeRemaining();
+ }
+
+ public static String getVersion() {
+ return (RouterVersion.VERSION + "-" + RouterVersion.BUILD);
+ }
+}
diff --git a/apps/desktopgui/src/router/configuration/PeerHelper.java b/apps/desktopgui/src/router/configuration/PeerHelper.java
new file mode 100644
index 0000000000..f593097055
--- /dev/null
+++ b/apps/desktopgui/src/router/configuration/PeerHelper.java
@@ -0,0 +1,165 @@
+package router.configuration;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import net.i2p.data.RouterAddress;
+import net.i2p.router.CommSystemFacade;
+import net.i2p.router.RouterContext;
+import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
+import net.i2p.router.transport.ntcp.NTCPAddress;
+import router.RouterHelper;
+
+/**
+ * Part of the code imported and adapted from the I2P Router Console (which is licensed as public domain)
+ */
+public class PeerHelper {
+ public static String getReachability() {
+ RouterContext context = RouterHelper.getContext();
+ if (context.router().getUptime() > 60*1000
+ && (!context.router().gracefulShutdownInProgress())
+ && !context.clientManager().isAlive())
+ return "ERROR: Client Manager I2CP Error - check logs"; // not a router problem but the user should know
+ if (!context.clock().getUpdatedSuccessfully())
+ return "ERROR: ClockSkew";
+ if (context.router().isHidden())
+ return "Hidden";
+
+ int status = context.commSystem().getReachabilityStatus();
+ switch (status) {
+ case CommSystemFacade.STATUS_OK:
+ RouterAddress ra = context.router().getRouterInfo().getTargetAddress("NTCP");
+ if (ra == null || (new NTCPAddress(ra)).isPubliclyRoutable())
+ return "OK";
+ return "ERROR: Private TCP Address";
+ case CommSystemFacade.STATUS_DIFFERENT:
+ return "ERROR: You are behind a symmetric NAT.";
+ case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
+ if (context.router().getRouterInfo().getTargetAddress("NTCP") != null)
+ return "WARNING: You are behind a firewall and have Inbound TCP Enabled";
+ if (((FloodfillNetworkDatabaseFacade)context.netDb()).floodfillEnabled())
+ return "WARNING: You are behind a firewall and are a floodfill router";
+ if (context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
+ return "WARNING: You are behind a firewall and are a fast router";
+ return "Firewalled";
+ case CommSystemFacade.STATUS_HOSED:
+ return "ERROR: The UDP port is already in use. Set i2np.udp.internalPort=xxxx to a different value in the advanced config and restart";
+ case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
+ default:
+ ra = context.router().getRouterInfo().getTargetAddress("UDP");
+ if (ra == null && context.router().getUptime() > 5*60*1000) {
+ if (context.getProperty(PROP_I2NP_NTCP_HOSTNAME) == null ||
+ context.getProperty(PROP_I2NP_NTCP_PORT) == null)
+ return "ERROR: UDP is disabled and the inbound TCP host/port combination is not set";
+ else
+ return "WARNING: You are behind a firewall and have UDP Disabled";
+ }
+ return "Testing";
+ }
+ }
+
+ /**
+ * How many peers we are talking to now
+ *
+ */
+ public static int getActivePeers() {
+ RouterContext context = RouterHelper.getContext();
+ if (context == null)
+ return 0;
+ else
+ return context.commSystem().countActivePeers();
+ }
+
+ public static void addActivePeerListener(ActionListener listener) {
+ synchronized(activePeerListeners) {
+ activePeerListeners.add(listener);
+ if(activePeerTimer == null) {
+ activePeerTimer = new Timer();
+ TimerTask t = new TimerTask() {
+ private int activePeers = 0;
+
+ @Override
+ public void run() {
+ int newActivePeers = getActivePeers();
+ if(!(activePeers == newActivePeers)) {
+ synchronized(activePeerListeners) {
+ for(int i=0; i reachabilityListeners = new ArrayList();
+ private static Timer reachabilityTimer = null;
+
+ private static List activePeerListeners = new ArrayList();
+ private static Timer activePeerTimer = null;
+
+ /** copied from various private components */
+ public final static String PROP_I2NP_UDP_PORT = "i2np.udp.port";
+ public final static String PROP_I2NP_INTERNAL_UDP_PORT = "i2np.udp.internalPort";
+ public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
+ public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
+ public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoip";
+ public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoport";
+}
diff --git a/apps/desktopgui/src/router/configuration/SpeedHandler.java b/apps/desktopgui/src/router/configuration/SpeedHandler.java
new file mode 100644
index 0000000000..235790792a
--- /dev/null
+++ b/apps/desktopgui/src/router/configuration/SpeedHandler.java
@@ -0,0 +1,34 @@
+package router.configuration;
+
+import net.i2p.router.RouterContext;
+import net.i2p.router.transport.FIFOBandwidthRefiller;
+import router.RouterHelper;
+
+/**
+ *
+ * @author mathias
+ */
+public class SpeedHandler {
+
+ public static void setInboundBandwidth(int kbytes) {
+ context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, "" + kbytes);
+ context.router().saveConfig();
+ }
+
+ public static void setOutboundBandwidth(int kbytes) {
+ context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, "" + kbytes);
+ context.router().saveConfig();
+ }
+
+ public static void setInboundBurstBandwidth(int kbytes) {
+ context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + kbytes);
+ context.router().saveConfig();
+ }
+
+ public static void setOutboundBurstBandwidth(int kbytes) {
+ context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + kbytes);
+ context.router().saveConfig();
+ }
+
+ private static final RouterContext context = RouterHelper.getContext();
+}
diff --git a/apps/desktopgui/src/router/configuration/SpeedHelper.java b/apps/desktopgui/src/router/configuration/SpeedHelper.java
new file mode 100644
index 0000000000..acad2adb14
--- /dev/null
+++ b/apps/desktopgui/src/router/configuration/SpeedHelper.java
@@ -0,0 +1,32 @@
+package router.configuration;
+
+/**
+ *
+ * @author mathias
+ */
+public class SpeedHelper {
+ public static final String USERTYPE_BROWSING = "Browsing";
+ public static final String USERTYPE_DOWNLOADING = "Downloading";
+
+ public static int calculateSpeed(String capable, String profile) {
+ int capableSpeed = Integer.parseInt(capable);
+ int advisedSpeed = capableSpeed;
+ if(capableSpeed > 1000) {
+ if(profile.equals(USERTYPE_BROWSING)) //Don't overdo usage for people just wanting to browse (we don't want to drive them away due to resource hogging)
+ advisedSpeed *= 0.6;
+ else if(profile.equals(USERTYPE_DOWNLOADING))
+ advisedSpeed *= 0.8;
+ }
+ else
+ advisedSpeed *= 0.6; //Lower available bandwidth: don't hog all the bandwidth
+ return advisedSpeed;
+ }
+
+ public static int calculateMonthlyUsage(int kbytes) {
+ return (int) ((((long)kbytes)*3600*24*31)/1000000);
+ }
+
+ public static int calculateSpeed(int gigabytes) {
+ return (int) (((long)gigabytes)*1000000/31/24/3600);
+ }
+}
diff --git a/apps/desktopgui/src/util/IntegerVerifier.java b/apps/desktopgui/src/util/IntegerVerifier.java
new file mode 100644
index 0000000000..74f87961da
--- /dev/null
+++ b/apps/desktopgui/src/util/IntegerVerifier.java
@@ -0,0 +1,32 @@
+package util;
+
+import javax.swing.InputVerifier;
+import javax.swing.JComponent;
+import javax.swing.JTextField;
+
+/**
+ *
+ * @author mathias
+ */
+
+public class IntegerVerifier extends InputVerifier {
+
+ @Override
+ public boolean verify(JComponent arg0) {
+ JTextField jtf = (JTextField) arg0;
+ return verify(jtf.getText());
+ }
+
+ @Override
+ public boolean shouldYieldFocus(JComponent input) {
+ return verify(input);
+ }
+
+ public static boolean verify(String s) {
+ for(int i=0;i '9' || s.charAt(i) < '0')
+ return false;
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/apps/i2psnark/java/build.xml b/apps/i2psnark/java/build.xml
index 02eee0aabc..9689fda068 100644
--- a/apps/i2psnark/java/build.xml
+++ b/apps/i2psnark/java/build.xml
@@ -18,7 +18,6 @@
-
@@ -32,7 +31,7 @@
srcdir="./src"
debug="true" deprecation="on" source="1.5" target="1.5"
destdir="./build/obj"
- classpath="../../../core/java/build/i2p.jar:../../../router/java/build/router.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
+ classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
diff --git a/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java b/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java
new file mode 100644
index 0000000000..eb157cb5e4
--- /dev/null
+++ b/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java
@@ -0,0 +1,44 @@
+/*
+ * Released into the public domain
+ * with no warranty of any kind, either expressed or implied.
+ */
+package org.klomp.snark;
+
+import java.util.Arrays;
+import java.util.Properties;
+
+import net.i2p.I2PAppContext;
+import net.i2p.client.I2PSessionException;
+import net.i2p.client.I2PClient;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSimpleClient;
+
+/**
+ * Connect via I2CP and ask the router the bandwidth limits.
+ *
+ * The call is blocking and returns null on failure.
+ * Timeout is set to 5 seconds in I2PSimpleSession but it should be much faster.
+ *
+ * @author zzz
+ */
+class BWLimits {
+
+ public static int[] getBWLimits(String host, int port) {
+ int[] rv = null;
+ try {
+ I2PClient client = new I2PSimpleClient();
+ Properties opts = new Properties();
+ opts.put(I2PClient.PROP_TCP_HOST, host);
+ opts.put(I2PClient.PROP_TCP_PORT, "" + port);
+ I2PSession session = client.createSession(null, opts);
+ session.connect();
+ rv = session.bandwidthLimits();
+ session.destroySession();
+ } catch (I2PSessionException ise) {}
+ return rv;
+ }
+
+ public static void main(String args[]) {
+ System.out.println(Arrays.toString(getBWLimits("127.0.0.1", 7654)));
+ }
+}
diff --git a/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java b/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
index 6d4aad1a31..2e45749b87 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
@@ -152,6 +152,7 @@ public class ConnectionAcceptor implements Runnable
_util.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
+ // catch oom?
}
try
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
index f10ef41d33..53c42dfadf 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
@@ -36,7 +36,6 @@ import java.util.Timer;
import java.util.TimerTask;
import net.i2p.I2PAppContext;
-import net.i2p.router.client.ClientManagerFacadeImpl;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
@@ -232,7 +231,7 @@ public class Snark
}
// Explicit shutdown.
- Runtime.getRuntime().removeShutdownHook(snarkhook);
+ //Runtime.getRuntime().removeShutdownHook(snarkhook);
snarkhook.start();
}
}
@@ -261,9 +260,9 @@ public class Snark
public Snark(I2PAppContext ctx, Properties opts, String torrent,
StorageListener slistener, boolean start, String rootDir) {
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
- String host = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST);
+ String host = opts.getProperty("i2cp.hostname");
int port = 0;
- String s = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_PORT);
+ String s = opts.getProperty("i2cp.port");
if (s != null) {
try {
port = Integer.parseInt(s);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index 7b62ace843..60c44f3d59 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -18,7 +18,6 @@ import java.util.TreeMap;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
-import net.i2p.router.RouterContext;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
@@ -81,8 +80,7 @@ public class SnarkManager implements Snark.CompleteListener {
I2PAppThread monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
- if (_context instanceof RouterContext)
- ((RouterContext)_context).router().addShutdownTask(new SnarkManagerShutdown());
+ _context.addShutdownTask(new SnarkManagerShutdown());
}
/** hook to I2PSnarkUtil for the servlet */
@@ -137,33 +135,32 @@ public class SnarkManager implements Snark.CompleteListener {
}
// now add sane defaults
if (!_config.containsKey(PROP_I2CP_HOST))
- _config.setProperty(PROP_I2CP_HOST, "localhost");
+ _config.setProperty(PROP_I2CP_HOST, "127.0.0.1");
if (!_config.containsKey(PROP_I2CP_PORT))
_config.setProperty(PROP_I2CP_PORT, "7654");
if (!_config.containsKey(PROP_I2CP_OPTS))
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=2 inbound.lengthVariance=0 outbound.length=2 outbound.lengthVariance=0 inbound.quantity=3 outbound.quantity=3");
if (!_config.containsKey(PROP_EEP_HOST))
- _config.setProperty(PROP_EEP_HOST, "localhost");
+ _config.setProperty(PROP_EEP_HOST, "127.0.0.1");
if (!_config.containsKey(PROP_EEP_PORT))
_config.setProperty(PROP_EEP_PORT, "4444");
if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
- if (!_config.containsKey(PROP_UPBW_MAX)) {
- try {
- if (_context instanceof RouterContext)
- _config.setProperty(PROP_UPBW_MAX, "" + (((RouterContext)_context).bandwidthLimiter().getOutboundKBytesPerSecond() / 2));
- else
- _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
- } catch (NoClassDefFoundError ncdfe) {
- _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
- }
- }
if (!_config.containsKey(PROP_DIR))
_config.setProperty(PROP_DIR, "i2psnark");
if (!_config.containsKey(PROP_AUTO_START))
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
updateConfig();
}
+
+ /** call from DirMonitor since loadConfig() is called before router I2CP is up */
+ private void getBWLimit() {
+ if (!_config.containsKey(PROP_UPBW_MAX)) {
+ int[] limits = BWLimits.getBWLimits(_util.getI2CPHost(), _util.getI2CPPort());
+ if (limits != null && limits[1] > 0)
+ _util.setMaxUpBW(limits[1]);
+ }
+ }
private void updateConfig() {
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
@@ -539,7 +536,7 @@ public class SnarkManager implements Snark.CompleteListener {
String announce = info.getAnnounce();
// basic validation of url
if ((!announce.startsWith("http://")) ||
- (announce.indexOf(".i2p/") < 0))
+ (announce.indexOf(".i2p/") < 0)) // need to do better than this
return "Non-i2p tracker in " + info.getName() + ", deleting it";
List files = info.getFiles();
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
@@ -620,6 +617,9 @@ public class SnarkManager implements Snark.CompleteListener {
_messages.remove(0);
}
+ // here because we need to delay until I2CP is up
+ // although the user will see the default until then
+ getBWLimit();
while (true) {
File dir = getDataDir();
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index d42d3b520d..52e1095739 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -202,10 +202,14 @@ public class I2PSnarkServlet extends HttpServlet {
} catch (IOException ioe) {
_log.warn("hrm: " + local, ioe);
}
- } else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
- _manager.addMessage("Fetching " + newURL);
- I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
- fetch.start();
+ } else if (newURL != null) {
+ if (newURL.startsWith("http://")) {
+ _manager.addMessage("Fetching " + newURL);
+ I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
+ fetch.start();
+ } else {
+ _manager.addMessage("Invalid URL - must start with http://");
+ }
} else {
// no file or URL specified
}
@@ -644,7 +648,7 @@ public class I2PSnarkServlet extends HttpServlet {
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String newURL = req.getParameter("newURL");
- if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "http://";
+ if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "";
String newFile = req.getParameter("newFile");
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
@@ -772,7 +776,7 @@ public class I2PSnarkServlet extends HttpServlet {
return bytes + "B";
else if (bytes < 5*1024*1024)
return ((bytes + 512)/1024) + "KB";
- else if (bytes < 5*1024*1024*1024l)
+ else if (bytes < 10*1024*1024*1024l)
return ((bytes + 512*1024)/(1024*1024)) + "MB";
else
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java
index 29f954fc4f..4c3da08988 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java
@@ -14,7 +14,7 @@ import net.i2p.util.Log;
*/
class BufferLogger implements Logging {
private final static Log _log = new Log(BufferLogger.class);
- private ByteArrayOutputStream _baos;
+ private ByteArrayOutputStream _baos; // should be final and use a factory. LINT
private boolean _ignore;
/**
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
index 411b2c4426..12940a81bc 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
@@ -59,13 +59,16 @@ class HTTPResponseOutputStream extends FilterOutputStream {
_buf1 = new byte[1];
}
+ @Override
public void write(int c) throws IOException {
_buf1[0] = (byte)c;
write(_buf1, 0, 1);
}
+ @Override
public void write(byte buf[]) throws IOException {
write(buf, 0, buf.length);
}
+ @Override
public void write(byte buf[], int off, int len) throws IOException {
if (_headerWritten) {
out.write(buf, off, len);
@@ -207,6 +210,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
out.write("\r\n".getBytes()); // end of the headers
}
+ @Override
public void close() throws IOException {
out.close();
}
@@ -303,11 +307,13 @@ class HTTPResponseOutputStream extends FilterOutputStream {
return true;
}
}
+ @Override
public String toString() {
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
}
}
+ @Override
public String toString() {
return super.toString() + ": " + _in;
}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
index b72ae18b31..015f5c9c58 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
@@ -62,6 +62,8 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
+import net.i2p.i2ptunnel.streamr.StreamrConsumer;
+import net.i2p.i2ptunnel.streamr.StreamrProducer;
import net.i2p.util.EventDispatcher;
import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log;
@@ -73,7 +75,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
private static long __tunnelId = 0;
private long _tunnelId;
private Properties _clientOptions;
- private List _sessions;
+ private final List _sessions;
public static final int PACKET_DELAY = 100;
@@ -87,7 +89,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
private static final String nocli_args[] = { "-nocli", "-die"};
- private List tasks = new ArrayList();
+ private final List tasks = new ArrayList();
private int next_task_id = 1;
private Set listeners = new HashSet();
@@ -234,6 +236,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
runServer(args, l);
} else if ("httpserver".equals(cmdname)) {
runHttpServer(args, l);
+ } else if ("ircserver".equals(cmdname)) {
+ runIrcServer(args, l);
} else if ("textserver".equals(cmdname)) {
runTextServer(args, l);
} else if ("client".equals(cmdname)) {
@@ -246,6 +250,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
runSOCKSTunnel(args, l);
} else if ("connectclient".equals(cmdname)) {
runConnectClient(args, l);
+ } else if ("streamrclient".equals(cmdname)) {
+ runStreamrClient(args, l);
+ } else if ("streamrserver".equals(cmdname)) {
+ runStreamrServer(args, l);
} else if ("config".equals(cmdname)) {
runConfig(args, l);
} else if ("listen_on".equals(cmdname)) {
@@ -383,6 +391,53 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
+ /**
+ * Same args as runServer
+ * (we should stop duplicating all this code...)
+ */
+ public void runIrcServer(String args[], Logging l) {
+ if (args.length == 3) {
+ InetAddress serverHost = null;
+ int portNum = -1;
+ File privKeyFile = null;
+ try {
+ serverHost = InetAddress.getByName(args[0]);
+ } catch (UnknownHostException uhe) {
+ l.log("unknown host");
+ _log.error(getPrefix() + "Error resolving " + args[0], uhe);
+ notifyEvent("serverTaskId", Integer.valueOf(-1));
+ return;
+ }
+
+ try {
+ portNum = Integer.parseInt(args[1]);
+ } catch (NumberFormatException nfe) {
+ l.log("invalid port");
+ _log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
+ notifyEvent("serverTaskId", Integer.valueOf(-1));
+ return;
+ }
+
+ privKeyFile = new File(args[2]);
+ if (!privKeyFile.canRead()) {
+ l.log("private key file does not exist");
+ _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
+ notifyEvent("serverTaskId", Integer.valueOf(-1));
+ return;
+ }
+ I2PTunnelServer serv = new I2PTunnelIRCServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this, this);
+ serv.setReadTimeout(readTimeout);
+ serv.startRunning();
+ addtask(serv);
+ notifyEvent("serverTaskId", Integer.valueOf(serv.getId()));
+ return;
+ } else {
+ l.log("server ");
+ l.log(" creates a server that sends all incoming data\n" + " of its destination to host:port.");
+ notifyEvent("serverTaskId", Integer.valueOf(-1));
+ }
+ }
+
/**
* Run the HTTP server pointing at the host and port specified using the private i2p
* destination loaded from the specified file, replacing the HTTP headers
@@ -497,14 +552,14 @@ public class I2PTunnel implements Logging, EventDispatcher {
* Integer port number if the client is listening
* sharedClient parameter is a String "true" or "false"
*
- * @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
+ * @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient [, privKeyFile]]}
* @param l logger to receive events and output
*/
public void runClient(String args[], Logging l) {
boolean isShared = true;
- if (args.length == 3)
+ if (args.length >= 3)
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
- if ( (args.length == 2) || (args.length == 3) ) {
+ if (args.length >= 2) {
int portNum = -1;
try {
portNum = Integer.parseInt(args[0]);
@@ -517,7 +572,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
I2PTunnelTask task;
ownDest = !isShared;
try {
- task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
+ String privateKeyFile = null;
+ if (args.length >= 4)
+ privateKeyFile = args[3];
+ task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
addtask(task);
notifyEvent("clientTaskId", Integer.valueOf(task.getId()));
} catch (IllegalArgumentException iae) {
@@ -526,7 +584,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("clientTaskId", Integer.valueOf(-1));
}
} else {
- l.log("client [,]|file:[ ]");
+ l.log("client [,]|file:[ ] []");
l.log(" creates a client that forwards port to the pubkey.\n"
+ " use 0 as port to get a free port assigned. If you specify\n"
+ " a comma delimited list of pubkeys, it will rotate among them\n"
@@ -548,9 +606,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
*/
public void runHttpClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 3) {
- int port = -1;
+ int clientPort = -1;
try {
- port = Integer.parseInt(args[0]);
+ clientPort = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
@@ -584,12 +642,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
I2PTunnelTask task;
ownDest = !isShared;
try {
- task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
+ task = new I2PTunnelHTTPClient(clientPort, l, ownDest, proxy, (EventDispatcher) this, this);
addtask(task);
notifyEvent("httpclientTaskId", Integer.valueOf(task.getId()));
} catch (IllegalArgumentException iae) {
- _log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae);
- l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
+ _log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ clientPort + "]", iae);
+ l.log("Invalid I2PTunnel configuration [" + host + ":" + clientPort + "]");
notifyEvent("httpclientTaskId", Integer.valueOf(-1));
}
} else {
@@ -665,11 +723,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
- * @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]}
+ * @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient [, privKeyFile]]}
* @param l logger to receive events and output
*/
public void runIrcClient(String args[], Logging l) {
- if (args.length >= 2 && args.length <= 3) {
+ if (args.length >= 2) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
@@ -696,7 +754,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
I2PTunnelTask task;
ownDest = !isShared;
try {
- task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
+ String privateKeyFile = null;
+ if (args.length >= 4)
+ privateKeyFile = args[3];
+ task = new I2PTunnelIRCClient(port, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
addtask(task);
notifyEvent("ircclientTaskId", Integer.valueOf(task.getId()));
} catch (IllegalArgumentException iae) {
@@ -705,7 +766,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
}
} else {
- l.log("ircclient []");
+ l.log("ircclient [ []]");
l.log(" creates a client that filter IRC protocol.");
l.log(" (optional) indicates if this client shares tunnels with other clients (true of false)");
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
@@ -751,6 +812,82 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
+ /**
+ * Streamr client
+ *
+ * @param args {targethost, targetport, destinationString}
+ * @param l logger to receive events and output
+ */
+ public void runStreamrClient(String args[], Logging l) {
+ if (args.length == 3) {
+ InetAddress host;
+ try {
+ host = InetAddress.getByName(args[0]);
+ } catch (UnknownHostException uhe) {
+ l.log("unknown host");
+ _log.error(getPrefix() + "Error resolving " + args[0], uhe);
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
+ return;
+ }
+
+ int port = -1;
+ try {
+ port = Integer.parseInt(args[1]);
+ } catch (NumberFormatException nfe) {
+ l.log("invalid port");
+ _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
+ return;
+ }
+
+ StreamrConsumer task = new StreamrConsumer(host, port, args[2], l, (EventDispatcher) this, this);
+ task.startRunning();
+ addtask(task);
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
+ } else {
+ l.log("streamrclient ");
+ l.log(" creates a tunnel that receives streaming data.");
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
+ }
+ }
+
+ /**
+ * Streamr server
+ *
+ * @param args {port, privkeyfile}
+ * @param l logger to receive events and output
+ */
+ public void runStreamrServer(String args[], Logging l) {
+ if (args.length == 2) {
+ int port = -1;
+ try {
+ port = Integer.parseInt(args[0]);
+ } catch (NumberFormatException nfe) {
+ l.log("invalid port");
+ _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
+ return;
+ }
+
+ File privKeyFile = new File(args[1]);
+ if (!privKeyFile.canRead()) {
+ l.log("private key file does not exist");
+ _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
+ notifyEvent("serverTaskId", Integer.valueOf(-1));
+ return;
+ }
+
+ StreamrProducer task = new StreamrProducer(port, privKeyFile, args[1], l, (EventDispatcher) this, this);
+ task.startRunning();
+ addtask(task);
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
+ } else {
+ l.log("streamrserver ");
+ l.log(" creates a tunnel that sends streaming data.");
+ notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
+ }
+ }
+
/**
* Specify the i2cp host and port
*
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
index 4739a07f47..12931f3a27 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
@@ -31,8 +31,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
*/
public I2PTunnelClient(int localPort, String destinations, Logging l,
boolean ownDest, EventDispatcher notifyThis,
- I2PTunnel tunnel) throws IllegalArgumentException {
- super(localPort, ownDest, l, notifyThis, "SynSender", tunnel);
+ I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
+ super(localPort, ownDest, l, notifyThis, "SynSender", tunnel, pkf);
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openClientResult", "error");
@@ -44,11 +44,11 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
while (tok.hasMoreTokens()) {
String destination = tok.nextToken();
try {
- Destination dest = I2PTunnel.destFromName(destination);
- if (dest == null)
+ Destination destN = I2PTunnel.destFromName(destination);
+ if (destN == null)
l.log("Could not resolve " + destination);
else
- dests.add(dest);
+ dests.add(destN);
} catch (DataFormatException dfe) {
l.log("Bad format parsing \"" + destination + "\"");
}
@@ -71,10 +71,10 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
public long getReadTimeout() { return readTimeout; }
protected void clientConnectionRun(Socket s) {
- Destination dest = pickDestination();
+ Destination destN = pickDestination();
I2PSocket i2ps = null;
try {
- i2ps = createI2PSocket(dest);
+ i2ps = createI2PSocket(destN);
i2ps.setReadTimeout(readTimeout);
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
} catch (Exception ex) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
index 38311eaf1d..c4eafde506 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
@@ -3,6 +3,7 @@
*/
package net.i2p.i2ptunnel;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
@@ -40,8 +41,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static volatile long __clientId = 0;
protected long _clientId;
- protected Object sockLock = new Object(); // Guards sockMgr and mySockets
- protected I2PSocketManager sockMgr;
+ protected final Object sockLock = new Object(); // Guards sockMgr and mySockets
+ protected I2PSocketManager sockMgr; // should be final and use a factory. LINT
protected List mySockets = new ArrayList();
protected Destination dest = null;
@@ -51,19 +52,20 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private ServerSocket ss;
- private Object startLock = new Object();
+ private final Object startLock = new Object();
private boolean startRunning = false;
- private Object closeLock = new Object();
+ // private Object closeLock = new Object();
- private byte[] pubkey;
+ // private byte[] pubkey;
private String handlerName;
+ private String privKeyFile;
- private Object conLock = new Object();
+ // private Object conLock = new Object();
/** List of Socket for those accept()ed but not yet started up */
- private List _waitingSockets = new ArrayList();
+ private List _waitingSockets = new ArrayList(); // should be final and use a factory. LINT
/** How many connections will we allow to be in the process of being built at once? */
private int _numConnectionBuilders;
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
@@ -91,18 +93,28 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
//}
+ public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l,
+ EventDispatcher notifyThis, String handlerName,
+ I2PTunnel tunnel) throws IllegalArgumentException {
+ this(localPort, ownDest, l, notifyThis, handlerName, tunnel, null);
+ }
+
/**
+ * @param pkf null to generate a transient key
+ *
* @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{
+ I2PTunnel tunnel, String pkf) throws IllegalArgumentException{
super(localPort + " (uninitialized)", notifyThis, tunnel);
_clientId = ++__clientId;
this.localPort = localPort;
this.l = l;
this.handlerName = handlerName + _clientId;
+ this.privKeyFile = pkf;
+
_context = tunnel.getContext();
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
@@ -114,26 +126,28 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// be looked up
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
- while (sockMgr == null) {
- synchronized (sockLock) {
- if (ownDest) {
- sockMgr = buildSocketManager();
- } else {
- sockMgr = getSocketManager();
+ boolean openNow = !Boolean.valueOf(tunnel.getClientOptions().getProperty("i2cp.delayOpen")).booleanValue();
+ if (openNow) {
+ while (sockMgr == null) {
+ synchronized (sockLock) {
+ if (ownDest) {
+ sockMgr = buildSocketManager();
+ } else {
+ sockMgr = getSocketManager();
+ }
+ }
+ if (sockMgr == null) {
+ _log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
+ try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
if (sockMgr == null) {
- _log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
- try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
+ l.log("Invalid I2CP configuration");
+ throw new IllegalArgumentException("Socket manager could not be created");
}
- }
- if (sockMgr == null) {
- l.log("Invalid I2CP configuration");
- throw new IllegalArgumentException("Socket manager could not be created");
- }
- l.log("I2P session created");
+ l.log("I2P session created");
- getTunnel().addSession(sockMgr.getSession());
+ } // else delay creating session until createI2PSocket() is called
Thread t = new I2PThread(this);
t.setName("Client " + _clientId);
@@ -153,7 +167,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
configurePool(tunnel);
if (open && listenerReady) {
- l.log("Ready! Port " + getLocalPort());
+ if (openNow)
+ l.log("Ready! Port " + getLocalPort());
+ else
+ l.log("Listening on port " + getLocalPort() + ", delaying tunnel open until required");
notifyEvent("openBaseClientResult", "ok");
} else {
l.log("Error listening - please see the logs!");
@@ -195,28 +212,36 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static I2PSocketManager socketManager;
protected synchronized I2PSocketManager getSocketManager() {
- return getSocketManager(getTunnel());
+ return getSocketManager(getTunnel(), this.privKeyFile);
}
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) {
+ return getSocketManager(tunnel, null);
+ }
+ protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel, String pkf) {
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);
+ if (s != null)
+ tunnel.removeSession(s);
+ socketManager = buildSocketManager(tunnel, pkf);
} 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");
- socketManager = buildSocketManager(tunnel);
+ socketManager = buildSocketManager(tunnel, pkf);
}
return socketManager;
}
protected I2PSocketManager buildSocketManager() {
- return buildSocketManager(getTunnel());
+ return buildSocketManager(getTunnel(), this.privKeyFile);
}
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel) {
+ return buildSocketManager(tunnel, null);
+ }
+ protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf) {
Properties props = new Properties();
props.putAll(tunnel.getClientOptions());
int portNum = 7654;
@@ -230,7 +255,22 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
I2PSocketManager sockManager = null;
while (sockManager == null) {
- sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
+ if (pkf != null) {
+ // Persistent client dest
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(pkf);
+ sockManager = I2PSocketManagerFactory.createManager(fis, tunnel.host, portNum, props);
+ } catch (IOException ioe) {
+ _log.error("Error opening key file", ioe);
+ // this is going to loop but if we break we'll get a NPE
+ } finally {
+ if (fis != null)
+ try { fis.close(); } catch (IOException ioe) {}
+ }
+ } else {
+ sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
+ }
if (sockManager == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
@@ -238,6 +278,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
}
sockManager.setName("Client");
+ tunnel.addSession(sockManager.getSession());
return sockManager;
}
@@ -302,6 +343,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
* @return a new I2PSocket
*/
public I2PSocket createI2PSocket(Destination dest) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
+ if (sockMgr == null) {
+ // we need this before getDefaultOptions()
+ sockMgr = getSocketManager();
+ }
return createI2PSocket(dest, getDefaultOptions());
}
@@ -322,6 +367,19 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
I2PSocket i2ps;
+ if (sockMgr == null) {
+ // delayed open - call get instead of build because the locking is up there
+ sockMgr = getSocketManager();
+ } else if (Boolean.valueOf(getTunnel().getClientOptions().getProperty("i2cp.newDestOnResume")).booleanValue()) {
+ synchronized(sockMgr) {
+ I2PSocketManager oldSockMgr = sockMgr;
+ // This will build a new socket manager and a new dest if the session is closed.
+ sockMgr = getSocketManager();
+ if (oldSockMgr != sockMgr) {
+ _log.warn("Built a new destination on resume");
+ }
+ }
+ } // else the old socket manager will reconnect the old session if necessary
i2ps = sockMgr.connect(dest, opt);
synchronized (sockLock) {
mySockets.add(i2ps);
@@ -374,8 +432,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
_context.statManager().addRateData("i2ptunnel.client.manageTime", total, total);
}
} catch (IOException ex) {
- _log.error("Error listening for connections on " + localPort, ex);
- notifyEvent("openBaseClientResult", "error");
+ if (open) {
+ _log.error("Error listening for connections on " + localPort, ex);
+ notifyEvent("openBaseClientResult", "error");
+ }
synchronized (sockLock) {
mySockets.clear();
}
@@ -456,20 +516,23 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// 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());
+ if (sockMgr != null) {
+ 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;
+ }
+ I2PSession session = sockMgr.getSession();
+ if (session != null) {
+ getTunnel().removeSession(session);
}
- return false;
- }
- I2PSession session = sockMgr.getSession();
- if (session != null) {
- getTunnel().removeSession(session);
}
l.log("Closing client " + toString());
+ open = false;
try {
if (ss != null) ss.close();
} catch (IOException ex) {
@@ -477,7 +540,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
return false;
}
l.log("Client closed.");
- open = false;
}
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java
index bf7ebf0a7a..d781866d95 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java
@@ -17,7 +17,6 @@ import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
-import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
@@ -55,7 +54,7 @@ import net.i2p.util.Log;
public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelConnectClient.class);
- private List _proxyList;
+ private final List _proxyList;
private final static byte[] ERR_DESTINATION_UNKNOWN =
("HTTP/1.1 503 Service Unavailable\r\n"+
@@ -116,12 +115,12 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
+ _proxyList = new ArrayList();
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openConnectClientResult", "error");
return;
}
- _proxyList = new ArrayList();
if (wwwProxy != null) {
StringTokenizer tok = new StringTokenizer(wwwProxy, ",");
while (tok.hasMoreTokens())
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
index b42376dbf7..277328363e 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
@@ -50,7 +50,7 @@ import net.i2p.util.Log;
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
- private List proxyList;
+ private final List proxyList;
private HashMap addressHelpers = new HashMap();
@@ -145,12 +145,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
+ proxyList = new ArrayList();
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openHTTPClientResult", "error");
return;
}
- proxyList = new ArrayList();
if (wwwProxy != null) {
StringTokenizer tok = new StringTokenizer(wwwProxy, ",");
while (tok.hasMoreTokens())
@@ -185,7 +185,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
/**
* create the default options (using the default timeout, etc)
- *
+ * unused?
*/
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
@@ -210,6 +210,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
+ // delayed start
+ if (sockMgr == null)
+ sockMgr = getSocketManager();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
@@ -285,7 +288,20 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
break;
}
host = request.substring(0, pos);
-
+
+ // parse port
+ int posPort = host.indexOf(":");
+ int port = 80;
+ if(posPort != -1) {
+ String[] parts = host.split(":");
+ host = parts[0];
+ try {
+ port = Integer.parseInt(parts[1]);
+ } catch(Exception exc) {
+ // TODO: log this
+ }
+ }
+
// Quick hack for foo.bar.i2p
if (host.toLowerCase().endsWith(".i2p")) {
// Destination gets the host name
@@ -386,6 +402,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
line = method + " " + request.substring(pos);
} else if (host.indexOf(".") != -1) {
+ // rebuild host
+ host = host + ":" + port;
// The request must be forwarded to a WWW proxy
if (_log.shouldLog(Log.DEBUG))
_log.debug("Before selecting outproxy for " + host);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java
index 9ade9021f5..6afc940f7d 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java
@@ -30,11 +30,13 @@ public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
}
+ @Override
protected OutputStream getSocketOut() throws IOException {
OutputStream raw = super.getSocketOut();
return new HTTPResponseOutputStream(raw);
}
+ @Override
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
try {
i2pin.close();
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
index 658dd5e327..d43639cb7e 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
@@ -59,6 +59,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
* Called by the thread pool of I2PSocket handlers
*
*/
+ @Override
protected void blockingHandle(I2PSocket socket) {
long afterAccept = getTunnel().getContext().clock().now();
long afterSocket = -1;
@@ -124,8 +125,17 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
+ try {
+ socket.close();
+ } catch (IOException ioe) {}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ex);
+ } catch (OutOfMemoryError oom) {
+ try {
+ socket.close();
+ } catch (IOException ioe) {}
+ if (_log.shouldLog(Log.ERROR))
+ _log.error("OOM in HTTP server", oom);
}
long afterHandle = getTunnel().getContext().clock().now();
@@ -162,7 +172,24 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
sender.start();
browserout = _browser.getOutputStream();
- serverin = _webserver.getInputStream();
+ // NPE seen here in 0.7-7, caused by addition of socket.close() in the
+ // catch (IOException ioe) block above in blockingHandle() ???
+ // CRIT [ad-130280.hc] net.i2p.util.I2PThread : Killing thread Thread-130280.hc
+ // java.lang.NullPointerException
+ // at java.io.FileInputStream.(FileInputStream.java:131)
+ // at java.net.SocketInputStream.(SocketInputStream.java:44)
+ // at java.net.PlainSocketImpl.getInputStream(PlainSocketImpl.java:401)
+ // at java.net.Socket$2.run(Socket.java:779)
+ // at java.security.AccessController.doPrivileged(Native Method)
+ // at java.net.Socket.getInputStream(Socket.java:776)
+ // at net.i2p.i2ptunnel.I2PTunnelHTTPServer$CompressedRequestor.run(I2PTunnelHTTPServer.java:174)
+ // at java.lang.Thread.run(Thread.java:619)
+ // at net.i2p.util.I2PThread.run(I2PThread.java:71)
+ try {
+ serverin = _webserver.getInputStream();
+ } catch (NullPointerException npe) {
+ throw new IOException("getInputStream NPE");
+ }
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
if (_log.shouldLog(Log.INFO))
@@ -221,7 +248,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
super(o);
}
+ @Override
protected boolean shouldCompress() { return true; }
+ @Override
protected void finishHeaders() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Including x-i2p-gzip as the content encoding in the response");
@@ -229,6 +258,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
super.finishHeaders();
}
+ @Override
protected void beginProcessing() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Beginning compression processing");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
index e6708aa21d..8b62702198 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
@@ -39,23 +39,23 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
Logging l,
boolean ownDest,
EventDispatcher notifyThis,
- I2PTunnel tunnel) throws IllegalArgumentException {
+ I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
super(localPort,
ownDest,
l,
notifyThis,
- "IRCHandler " + (++__clientId), tunnel);
+ "IRCHandler " + (++__clientId), tunnel, pkf);
StringTokenizer tok = new StringTokenizer(destinations, ",");
dests = new ArrayList(1);
while (tok.hasMoreTokens()) {
String destination = tok.nextToken();
try {
- Destination dest = I2PTunnel.destFromName(destination);
- if (dest == null)
+ Destination destN = I2PTunnel.destFromName(destination);
+ if (destN == null)
l.log("Could not resolve " + destination);
else
- dests.add(dest);
+ dests.add(destN);
} catch (DataFormatException dfe) {
l.log("Bad format parsing \"" + destination + "\"");
}
@@ -83,9 +83,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
StringBuffer expectedPong = new StringBuffer();
- Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
+ Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " in");
in.start();
- Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
+ Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " out");
out.start();
} catch (Exception ex) {
if (_log.shouldLog(Log.ERROR))
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java
new file mode 100644
index 0000000000..2e209dbd74
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java
@@ -0,0 +1,188 @@
+package net.i2p.i2ptunnel;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Properties;
+
+import net.i2p.client.streaming.I2PSocket;
+import net.i2p.crypto.SHA256Generator;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
+import net.i2p.data.Hash;
+import net.i2p.data.Base32;
+import net.i2p.util.EventDispatcher;
+import net.i2p.util.Log;
+
+/**
+ * Simple extension to the I2PTunnelServer that filters the registration
+ * sequence to pass the destination hash of the client through as the hostname,
+ * so an IRC Server may track users across nick changes.
+ *
+ * Of course, this requires the ircd actually use the hostname sent by
+ * the client rather than the IP. It is common for ircds to ignore the
+ * hostname in the USER message (unless it's coming from another server)
+ * since it is easily spoofed. So you have to fix or, if you are lucky,
+ * configure your ircd first. At least in unrealircd and ngircd this is
+ * not configurable.
+ *
+ * There are three options for mangling the desthash. Put the option in the
+ * "custom options" section of i2ptunnel.
+ * - ircserver.cloakKey unset: Cloak with a random value that is persistent for
+ * the life of this tunnel. This is the default.
+ * - ircserver.cloakKey=somepassphrase: Cloak with the hash of the passphrase. Use this to
+ * have consistent mangling across restarts, or to
+ * have multiple IRC servers cloak consistently to
+ * be able to track users even when they switch servers.
+ * Note: don't quote or put spaces in the passphrase,
+ * the i2ptunnel gui can't handle it.
+ * - ircserver.fakeHostname=%f.b32.i2p: Set the fake hostname sent by I2PTunnel,
+ * %f is the full B32 destination hash
+ * %c is the cloaked hash.
+ *
+ * There is no outbound filtering.
+ *
+ * @author zzz
+ */
+public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
+ public static final String PROP_CLOAK="ircserver.cloakKey";
+ public static final String PROP_HOSTNAME="ircserver.fakeHostname";
+ public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
+
+ private static final Log _log = new Log(I2PTunnelIRCServer.class);
+
+
+ /**
+ * @throws IllegalArgumentException if the I2PTunnel does not contain
+ * valid config to contact the router
+ */
+
+ public I2PTunnelIRCServer(InetAddress host, int port, File privkey, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
+ super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
+ initCloak(tunnel);
+ }
+
+ /** generate a random 32 bytes, or the hash of the passphrase */
+ private void initCloak(I2PTunnel tunnel) {
+ Properties opts = tunnel.getClientOptions();
+ String passphrase = opts.getProperty(PROP_CLOAK);
+ if (passphrase == null) {
+ this.cloakKey = new byte[Hash.HASH_LENGTH];
+ tunnel.getContext().random().nextBytes(this.cloakKey);
+ } else {
+ this.cloakKey = SHA256Generator.getInstance().calculateHash(passphrase.trim().getBytes()).getData();
+ }
+
+ this.hostname = opts.getProperty(PROP_HOSTNAME, PROP_HOSTNAME_DEFAULT);
+ }
+
+ @Override
+ protected void blockingHandle(I2PSocket socket) {
+ try {
+ // give them 15 seconds to send in the request
+ socket.setReadTimeout(15*1000);
+ InputStream in = socket.getInputStream();
+ String modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
+ socket.setReadTimeout(readTimeout);
+ Socket s = new Socket(remoteHost, remotePort);
+ new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null);
+ } catch (SocketException ex) {
+ try {
+ socket.close();
+ } catch (IOException ioe) {
+ if (_log.shouldLog(Log.ERROR))
+ _log.error("Error while closing the received i2p con", ex);
+ }
+ } catch (IOException ex) {
+ try {
+ socket.close();
+ } catch (IOException ioe) {}
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Error while receiving the new IRC Connection", ex);
+ } catch (OutOfMemoryError oom) {
+ try {
+ socket.close();
+ } catch (IOException ioe) {}
+ if (_log.shouldLog(Log.ERROR))
+ _log.error("OOM in IRC server", oom);
+ }
+ }
+
+ /**
+ * (Optionally) append 32 bytes of crap to the destination then return
+ * the first few characters of the hash of the whole thing, + ".i2p".
+ * Or do we want the full hash if the ircd is going to use this for
+ * nickserv auto-login? Or even Base32 if it will be used in a
+ * case-insensitive manner?
+ *
+ */
+ String cloakDest(Destination d) {
+ String hf;
+ String hc;
+
+ byte[] b = new byte[d.size() + this.cloakKey.length];
+ System.arraycopy(b, 0, d.toByteArray(), 0, d.size());
+ System.arraycopy(b, d.size(), this.cloakKey, 0, this.cloakKey.length);
+ hc = Base32.encode(SHA256Generator.getInstance().calculateHash(b).getData());
+
+ hf = Base32.encode(d.calculateHash().getData());
+
+ return this.hostname.replace("%f", hf).replace("%c", hc);
+ }
+
+ /** keep reading until we see USER or SERVER */
+ private String filterRegistration(InputStream in, String newHostname) throws IOException {
+ StringBuffer buf = new StringBuffer(128);
+ int lineCount = 0;
+
+ while (true) {
+ String s = DataHelper.readLine(in);
+ if (s == null)
+ throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
+ if (++lineCount > 10)
+ throw new IOException("Too many lines before USER or SERVER, giving up");
+ s = s.trim();
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Got line: " + s);
+
+ String field[]=s.split(" ",5);
+ String command;
+ int idx=0;
+
+ if(field[0].charAt(0)==':')
+ idx++;
+
+ try {
+ command = field[idx++];
+ } catch (IndexOutOfBoundsException ioobe) {
+ // wtf, server sent borked command?
+ throw new IOException("Dropping defective message: index out of bounds while extracting command.");
+ }
+
+ if ("USER".equalsIgnoreCase(command)) {
+ if (field.length < idx + 4)
+ throw new IOException("Too few parameters in USER message: " + s);
+ // USER zzz1 hostname localhost :zzz
+ // =>
+ // USER zzz1 abcd1234.i2p localhost :zzz
+ // this whole class is for these two lines...
+ buf.append("USER ").append(field[idx]).append(' ').append(newHostname);
+ buf.append(' ');
+ buf.append(field[idx+2]).append(' ').append(field[idx+3]).append("\r\n");
+ break;
+ }
+ buf.append(s).append("\r\n");
+ if ("SERVER".equalsIgnoreCase(command))
+ break;
+ }
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("All done, sending: " + buf.toString());
+ return buf.toString();
+ }
+
+ private byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
+ private String hostname;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
index f237b97efb..76480a9403 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
@@ -36,7 +36,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
private Socket s;
private I2PSocket i2ps;
- Object slock, finishLock = new Object();
+ final Object slock, finishLock = new Object();
boolean finished = false;
HashMap ostreams, sockets;
byte[] initialI2PData;
@@ -114,6 +114,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
protected InputStream getSocketIn() throws IOException { return s.getInputStream(); }
protected OutputStream getSocketOut() throws IOException { return s.getOutputStream(); }
+ @Override
public void run() {
try {
InputStream in = getSocketIn();
@@ -239,6 +240,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
start();
}
+ @Override
public void run() {
String from = i2ps.getThisDestination().calculateHash().toBase64().substring(0,6);
String to = i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
index fa3478c71d..76246d7b66 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
@@ -18,8 +18,6 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
-import net.i2p.client.I2PClient;
-import net.i2p.client.I2PClientFactory;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
@@ -36,8 +34,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
protected I2PSocketManager sockMgr;
protected I2PServerSocket i2pss;
- private Object lock = new Object();
- protected Object slock = new Object();
+ private final Object lock = new Object();
+ protected final Object slock = new Object();
protected InetAddress remoteHost;
protected int remotePort;
@@ -203,17 +201,17 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
public void run() {
if (shouldUsePool()) {
- I2PServerSocket i2pss = sockMgr.getServerSocket();
+ I2PServerSocket i2pS_S = sockMgr.getServerSocket();
int handlers = getHandlerCount();
for (int i = 0; i < handlers; i++) {
- I2PThread handler = new I2PThread(new Handler(i2pss), "Handle Server " + i);
+ I2PThread handler = new I2PThread(new Handler(i2pS_S), "Handle Server " + i);
handler.start();
}
} else {
- I2PServerSocket i2pss = sockMgr.getServerSocket();
+ I2PServerSocket i2pS_S = sockMgr.getServerSocket();
while (true) {
try {
- final I2PSocket i2ps = i2pss.accept();
+ final I2PSocket i2ps = i2pS_S.accept();
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
new I2PThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start();
} catch (I2PException ipe) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java
index 6aa8ca18f7..4a6a0bb66e 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java
@@ -73,6 +73,7 @@ public abstract class I2PTunnelTask implements EventDispatcher {
public void reportAbuse(I2PSession session, int severity) {
}
+ @Override
public String toString() {
return name;
}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
index 019bc7826c..f57ecd23d1 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
@@ -37,11 +37,11 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
private String command;
private long timeout = PING_TIMEOUT;
- private Object simulLock = new Object();
+ private final Object simulLock = new Object();
private int simulPings = 0;
private long lastPingTime = 0;
- private Object lock = new Object(), slock = new Object();
+ private final Object lock = new Object(), slock = new Object();
//public I2Ping(String cmd, Logging l,
// boolean ownDest) {
@@ -197,6 +197,7 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
start();
}
+ @Override
public void run() {
try {
Destination dest = I2PTunnel.destFromName(destination);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
index 3c9640ce5a..419e5a899d 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
@@ -58,7 +58,7 @@ public class TunnelController implements Logging {
setConfig(config, prefix);
_messages = new ArrayList(4);
_running = false;
- if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
+ if (createKey && (getType().endsWith("server") || getPersistentClientKey()))
createPrivateKey();
_starting = getStartOnLoad();
}
@@ -134,6 +134,8 @@ public class TunnelController implements Logging {
_log.warn("Cannot start the tunnel - no type specified");
return;
}
+ setI2CPOptions();
+ setSessionOptions();
if ("httpclient".equals(type)) {
startHttpClient();
} else if("ircclient".equals(type)) {
@@ -144,19 +146,26 @@ public class TunnelController implements Logging {
startConnectClient();
} else if ("client".equals(type)) {
startClient();
+ } else if ("streamrclient".equals(type)) {
+ startStreamrClient();
} else if ("server".equals(type)) {
startServer();
} else if ("httpserver".equals(type)) {
startHttpServer();
+ } else if ("ircserver".equals(type)) {
+ startIrcServer();
+ } else if ("streamrserver".equals(type)) {
+ startStreamrServer();
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Cannot start tunnel - unknown type [" + type + "]");
+ return;
}
+ acquire();
+ _running = true;
}
private void startHttpClient() {
- setI2CPOptions();
- setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
@@ -165,13 +174,9 @@ public class TunnelController implements Logging {
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
- acquire();
- _running = true;
}
private void startConnectClient() {
- setI2CPOptions();
- setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
@@ -180,31 +185,51 @@ public class TunnelController implements Logging {
_tunnel.runConnectClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runConnectClient(new String[] { listenPort, sharedClient, proxyList }, this);
- acquire();
- _running = true;
}
private void startIrcClient() {
- setI2CPOptions();
- setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
String sharedClient = getSharedClient();
- _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
- acquire();
- _running = true;
+ if (getPersistentClientKey()) {
+ String privKeyFile = getPrivKeyFile();
+ _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this);
+ } else {
+ _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
+ }
}
private void startSocksClient() {
- setI2CPOptions();
- setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String sharedClient = getSharedClient();
_tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this);
- acquire();
- _running = true;
+ }
+
+ /*
+ * Streamr client is a UDP server, use the listenPort field for targetPort
+ * and the listenOnInterface field for the targetHost
+ */
+ private void startStreamrClient() {
+ String targetHost = getListenOnInterface();
+ String targetPort = getListenPort();
+ String dest = getTargetDestination();
+ _tunnel.runStreamrClient(new String[] { targetHost, targetPort, dest }, this);
+ }
+
+ /**
+ * Streamr server is a UDP client, use the targetPort field for listenPort
+ * and the targetHost field for the listenOnInterface
+ */
+ private void startStreamrServer() {
+ String listenOn = getTargetHost();
+ if ( (listenOn != null) && (listenOn.length() > 0) ) {
+ _tunnel.runListenOn(new String[] { listenOn }, this);
+ }
+ String listenPort = getTargetPort();
+ String privKeyFile = getPrivKeyFile();
+ _tunnel.runStreamrServer(new String[] { listenPort, privKeyFile }, this);
}
/**
@@ -240,38 +265,38 @@ public class TunnelController implements Logging {
}
private void startClient() {
- setI2CPOptions();
- setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
String sharedClient = getSharedClient();
- _tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
- acquire();
- _running = true;
+ if (getPersistentClientKey()) {
+ String privKeyFile = getPrivKeyFile();
+ _tunnel.runClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this);
+ } else {
+ _tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
+ }
}
private void startServer() {
- setI2CPOptions();
- setSessionOptions();
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String privKeyFile = getPrivKeyFile();
_tunnel.runServer(new String[] { targetHost, targetPort, privKeyFile }, this);
- acquire();
- _running = true;
}
private void startHttpServer() {
- setI2CPOptions();
- setSessionOptions();
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String spoofedHost = getSpoofedHost();
String privKeyFile = getPrivKeyFile();
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
- acquire();
- _running = true;
+ }
+
+ private void startIrcServer() {
+ String targetHost = getTargetHost();
+ String targetPort = getTargetPort();
+ String privKeyFile = getPrivKeyFile();
+ _tunnel.runIrcServer(new String[] { targetHost, targetPort, privKeyFile }, this);
}
private void setListenOn() {
@@ -380,6 +405,7 @@ public class TunnelController implements Logging {
public String getProxyList() { return _config.getProperty("proxyList"); }
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
+ public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("option.persistentClientKey")).booleanValue(); }
public String getMyDestination() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
index 3b6e765399..16f418be9e 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
@@ -40,7 +40,7 @@ public class TunnelControllerGroup {
* no more tunnels are using it)
*
*/
- private Map _sessions;
+ private final Map _sessions;
public static TunnelControllerGroup getInstance() {
synchronized (TunnelControllerGroup.class) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java
new file mode 100644
index 0000000000..3c63758c13
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/MultiSink.java
@@ -0,0 +1,35 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.util.Map;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.Log;
+
+/**
+ * Sends to one of many Sinks
+ * @author zzz modded from streamr/MultiSource
+ */
+public class MultiSink implements Source, Sink {
+ private static final Log _log = new Log(MultiSink.class);
+
+ public MultiSink(Map cache) {
+ this.cache = cache;
+ }
+
+ /** Don't use this - put sinks in the cache */
+ public void setSink(Sink sink) {}
+
+ public void start() {}
+
+ public void send(Destination from, byte[] data) {
+ Sink s = this.cache.get(from);
+ if (s == null) {
+ _log.error("No where to go for " + from.calculateHash().toBase64().substring(0, 6));
+ return;
+ }
+ s.send(from, data);
+ }
+
+ private Map cache;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java
new file mode 100644
index 0000000000..fbdf2939d1
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java
@@ -0,0 +1,35 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.util.Map;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.Log;
+
+/**
+ * Track who the reply goes to
+ * @author zzz
+ */
+public class ReplyTracker implements Source, Sink {
+ private static final Log _log = new Log(MultiSink.class);
+
+ public ReplyTracker(Sink reply, Map cache) {
+ this.reply = reply;
+ this.cache = cache;
+ }
+
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {}
+
+ public void send(Destination to, byte[] data) {
+ this.cache.put(to, this.reply);
+ this.sink.send(to, data);
+ }
+
+ private Sink reply;
+ private Map cache;
+ private Sink sink;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java
new file mode 100644
index 0000000000..23ec70c3fd
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java
@@ -0,0 +1,284 @@
+/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
+ * with an additional exception. For further details, see the
+ * licensing terms in I2PTunnel.java.
+ *
+ * Copyright (c) 2004 by human
+ */
+package net.i2p.i2ptunnel.socks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.List;
+
+import net.i2p.I2PAppContext;
+import net.i2p.I2PException;
+import net.i2p.client.streaming.I2PSocket;
+import net.i2p.data.DataFormatException;
+import net.i2p.i2ptunnel.I2PTunnel;
+import net.i2p.util.HexDump;
+import net.i2p.util.Log;
+
+/*
+ * Class that manages SOCKS 4/4a connections, and forwards them to
+ * destination hosts or (eventually) some outproxy.
+ *
+ * @author zzz modded from SOCKS5Server
+ */
+public class SOCKS4aServer extends SOCKSServer {
+ private static final Log _log = new Log(SOCKS4aServer.class);
+
+ private Socket clientSock = null;
+ private boolean setupCompleted = false;
+
+ /**
+ * Create a SOCKS4a server that communicates with the client using
+ * the specified socket. This method should not be invoked
+ * directly: new SOCKS4aServer objects should be created by using
+ * SOCKSServerFactory.createSOCSKServer(). It is assumed that the
+ * SOCKS VER field has been stripped from the input stream of the
+ * client socket.
+ *
+ * @param clientSock client socket
+ */
+ public SOCKS4aServer(Socket clientSock) {
+ this.clientSock = clientSock;
+ }
+
+ public Socket getClientSocket() throws SOCKSException {
+ setupServer();
+
+ return clientSock;
+ }
+
+ protected void setupServer() throws SOCKSException {
+ if (setupCompleted) { return; }
+
+ DataInputStream in;
+ DataOutputStream out;
+ try {
+ in = new DataInputStream(clientSock.getInputStream());
+ out = new DataOutputStream(clientSock.getOutputStream());
+
+ manageRequest(in, out);
+ } catch (IOException e) {
+ throw new SOCKSException("Connection error (" + e.getMessage() + ")");
+ }
+
+ setupCompleted = true;
+ }
+
+ /**
+ * SOCKS4a request management. This method assumes that all the
+ * stuff preceding or enveloping the actual request
+ * has been stripped out of the input/output streams.
+ */
+ private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
+
+ int command = in.readByte() & 0xff;
+ switch (command) {
+ case Command.CONNECT:
+ break;
+ case Command.BIND:
+ _log.debug("BIND command is not supported!");
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ throw new SOCKSException("BIND command not supported");
+ default:
+ _log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ throw new SOCKSException("Invalid command in request");
+ }
+
+ connPort = in.readUnsignedShort();
+ if (connPort == 0) {
+ _log.debug("trying to connect to TCP port 0? Dropping!");
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ throw new SOCKSException("Invalid port number in request");
+ }
+
+ connHostName = new String("");
+ boolean alreadyWarned = false;
+ for (int i = 0; i < 4; ++i) {
+ int octet = in.readByte() & 0xff;
+ connHostName += Integer.toString(octet);
+ if (i != 3) {
+ connHostName += ".";
+ if (octet != 0 && !alreadyWarned) {
+ _log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
+ alreadyWarned = true;
+ }
+ }
+ }
+
+ // discard user name
+ readString(in);
+
+ // SOCKS 4a
+ if (connHostName.startsWith("0.0.0.") && !connHostName.equals("0.0.0.0"))
+ connHostName = readString(in);
+ }
+
+ private String readString(DataInputStream in) throws IOException {
+ StringBuffer sb = new StringBuffer(16);
+ char c;
+ while ((c = (char) (in.readByte() & 0xff)) != 0)
+ sb.append(c);
+ return sb.toString();
+ }
+
+ protected void confirmConnection() throws SOCKSException {
+ DataInputStream in;
+ DataOutputStream out;
+ try {
+ out = new DataOutputStream(clientSock.getOutputStream());
+
+ sendRequestReply(Reply.SUCCEEDED, InetAddress.getByName("127.0.0.1"), 1, out);
+ } catch (IOException e) {
+ throw new SOCKSException("Connection error (" + e.getMessage() + ")");
+ }
+ }
+
+ /**
+ * Send the specified reply to a request of the client. Either
+ * one of inetAddr or domainName can be null, depending on
+ * addressType.
+ */
+ private void sendRequestReply(int replyCode, InetAddress inetAddr,
+ int bindPort, DataOutputStream out) throws IOException {
+ ByteArrayOutputStream reps = new ByteArrayOutputStream();
+ DataOutputStream dreps = new DataOutputStream(reps);
+
+ // Reserved byte, should be 0x00
+ dreps.write(0x00);
+ dreps.write(replyCode);
+ dreps.writeShort(bindPort);
+ dreps.write(inetAddr.getAddress());
+
+ byte[] reply = reps.toByteArray();
+
+ if (_log.shouldLog(Log.DEBUG)) {
+ _log.debug("Sending request reply:\n" + HexDump.dump(reply));
+ }
+
+ out.write(reply);
+ }
+
+ /**
+ * Get an I2PSocket that can be used to send/receive 8-bit clean data
+ * to/from the destination of the SOCKS connection.
+ *
+ * @return an I2PSocket connected with the destination
+ */
+ public I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException {
+ setupServer();
+
+ if (connHostName == null) {
+ _log.error("BUG: destination host name has not been initialized!");
+ throw new SOCKSException("BUG! See the logs!");
+ }
+ if (connPort == 0) {
+ _log.error("BUG: destination port has not been initialized!");
+ throw new SOCKSException("BUG! See the logs!");
+ }
+
+ DataOutputStream out; // for errors
+ try {
+ out = new DataOutputStream(clientSock.getOutputStream());
+ } catch (IOException e) {
+ throw new SOCKSException("Connection error (" + e.getMessage() + ")");
+ }
+
+ // FIXME: here we should read our config file, select an
+ // outproxy, and instantiate the proper socket class that
+ // handles the outproxy itself (SOCKS4a, SOCKS4a, HTTP CONNECT...).
+ I2PSocket destSock;
+
+ try {
+ if (connHostName.toLowerCase().endsWith(".i2p") ||
+ connHostName.toLowerCase().endsWith(".onion")) {
+ _log.debug("connecting to " + connHostName + "...");
+ // Let's not due a new Dest for every request, huh?
+ //I2PSocketManager sm = I2PSocketManagerFactory.createManager();
+ //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
+ destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
+ } else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
+ String err = "No localhost accesses allowed through the Socks Proxy";
+ _log.error(err);
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException(err);
+ } else if (connPort == 80) {
+ // rewrite GET line to include hostname??? or add Host: line???
+ // or forward to local eepProxy (but that's a Socket not an I2PSocket)
+ // use eepProxy configured outproxies?
+ String err = "No handler for HTTP outproxy implemented - to: " + connHostName;
+ _log.error(err);
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException(err);
+ } else {
+ List proxies = t.getProxies(connPort);
+ if (proxies == null || proxies.size() <= 0) {
+ String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
+ _log.error(err);
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException(err);
+ }
+ int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
+ String proxy = proxies.get(p);
+ _log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
+ // this isn't going to work, these need to be socks outproxies so we need
+ // to do a socks session to them?
+ destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy));
+ }
+ confirmConnection();
+ _log.debug("connection confirmed - exchanging data...");
+ } catch (DataFormatException e) {
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException("Error in destination format");
+ } catch (SocketException e) {
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException("Error connecting ("
+ + e.getMessage() + ")");
+ } catch (IOException e) {
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException("Error connecting ("
+ + e.getMessage() + ")");
+ } catch (I2PException e) {
+ try {
+ sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException("Error connecting ("
+ + e.getMessage() + ")");
+ }
+
+ return destSock;
+ }
+
+ /*
+ * Some namespaces to enclose SOCKS protocol codes
+ */
+ private static class Command {
+ private static final int CONNECT = 0x01;
+ private static final int BIND = 0x02;
+ }
+
+ private static class Reply {
+ private static final int SUCCEEDED = 0x5a;
+ private static final int CONNECTION_REFUSED = 0x5b;
+ }
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java
index 38c50f2661..cc397c414c 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java
@@ -13,12 +13,15 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
@@ -67,7 +70,8 @@ public class SOCKS5Server extends SOCKSServer {
out = new DataOutputStream(clientSock.getOutputStream());
init(in, out);
- manageRequest(in, out);
+ if (manageRequest(in, out) == Command.UDP_ASSOCIATE)
+ handleUDP(in, out);
} catch (IOException e) {
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
}
@@ -111,7 +115,7 @@ public class SOCKS5Server extends SOCKSServer {
* initialization, integrity/confidentiality encapsulations, etc)
* has been stripped out of the input/output streams.
*/
- private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
+ private int manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
int socksVer = in.readByte() & 0xff;
if (socksVer != SOCKS_VERSION_5) {
_log.debug("error in SOCKS5 request (protocol != 5? wtf?)");
@@ -127,9 +131,12 @@ public class SOCKS5Server extends SOCKSServer {
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("BIND command not supported");
case Command.UDP_ASSOCIATE:
+ /*** if(!Boolean.valueOf(tunnel.getOptions().getProperty("i2ptunnel.socks.allowUDP")).booleanValue()) {
_log.debug("UDP ASSOCIATE command is not supported!");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("UDP ASSOCIATE command not supported");
+ ***/
+ break;
default:
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
@@ -152,7 +159,8 @@ public class SOCKS5Server extends SOCKSServer {
connHostName += ".";
}
}
- _log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
+ if (command != Command.UDP_ASSOCIATE)
+ _log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
break;
case AddressType.DOMAINNAME:
{
@@ -168,9 +176,12 @@ public class SOCKS5Server extends SOCKSServer {
_log.debug("DOMAINNAME address type in request: " + connHostName);
break;
case AddressType.IPV6:
- _log.warn("IP V6 address type in request! Is your client secure?" + " (IPv6 is not supported, anyway :-)");
- sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
- throw new SOCKSException("IPV6 addresses not supported");
+ if (command != Command.UDP_ASSOCIATE) {
+ _log.warn("IP V6 address type in request! Is your client secure?" + " (IPv6 is not supported, anyway :-)");
+ sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
+ throw new SOCKSException("IPV6 addresses not supported");
+ }
+ break;
default:
_log.debug("unknown address type in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
@@ -183,6 +194,7 @@ public class SOCKS5Server extends SOCKSServer {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid port number in request");
}
+ return command;
}
protected void confirmConnection() throws SOCKSException {
@@ -293,6 +305,13 @@ public class SOCKS5Server extends SOCKSServer {
// Let's not due a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
+ Destination dest = I2PTunnel.destFromName(connHostName);
+ if (dest == null) {
+ try {
+ sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
+ } catch (IOException ioe) {}
+ throw new SOCKSException("Host not found");
+ }
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
String err = "No localhost accesses allowed through the Socks Proxy";
@@ -358,6 +377,59 @@ public class SOCKS5Server extends SOCKSServer {
return destSock;
}
+ // This isn't really the right place for this, we can't stop the tunnel once it starts.
+ static SOCKSUDPTunnel _tunnel;
+ static final Object _startLock = new Object();
+ static byte[] dummyIP = new byte[4];
+ /**
+ * We got a UDP associate command.
+ * Loop here looking for more, never return normally,
+ * or else I2PSocksTunnel will create a streaming lib connection.
+ *
+ * Do UDP Socks clients actually send more than one Associate request?
+ * RFC 1928 isn't clear... maybe not.
+ */
+ private void handleUDP(DataInputStream in, DataOutputStream out) throws SOCKSException {
+ List ports = new ArrayList(1);
+ synchronized (_startLock) {
+ if (_tunnel == null) {
+ // tunnel options?
+ _tunnel = new SOCKSUDPTunnel(new I2PTunnel());
+ _tunnel.startRunning();
+ }
+ }
+ while (true) {
+ // Set it up. connHostName and connPort are the client's info.
+ InetAddress ia = null;
+ try {
+ ia = InetAddress.getByAddress(connHostName, dummyIP);
+ } catch (UnknownHostException uhe) {} // won't happen, no resolving done here
+ int myPort = _tunnel.add(ia, connPort);
+ ports.add(Integer.valueOf(myPort));
+ try {
+ sendRequestReply(Reply.SUCCEEDED, AddressType.IPV4, InetAddress.getByName("127.0.0.1"), null, myPort, out);
+ } catch (IOException ioe) { break; }
+
+ // wait for more ???
+ try {
+ int command = manageRequest(in, out);
+ // don't do this...
+ if (command != Command.UDP_ASSOCIATE)
+ break;
+ } catch (IOException ioe) { break; }
+ catch (SOCKSException ioe) { break; }
+ }
+
+ for (Integer i : ports)
+ _tunnel.remove(i);
+
+ // Prevent I2PSocksTunnel from calling getDestinationI2PSocket() above
+ // to create a streaming lib connection...
+ // This isn't very elegant...
+ //
+ throw new SOCKSException("End of UDP Processing");
+ }
+
/*
* Some namespaces to enclose SOCKS protocol codes
*/
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java
new file mode 100644
index 0000000000..763b9aa10a
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSHeader.java
@@ -0,0 +1,89 @@
+package net.i2p.i2ptunnel.socks;
+
+import net.i2p.data.Base32;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.I2PTunnel;
+
+/**
+ * Save the SOCKS header from a datagram
+ * Ref: RFC 1928
+ *
+ * @author zzz
+ */
+public class SOCKSHeader {
+
+ /**
+ * @param data the whole packet
+ */
+ public SOCKSHeader(byte[] data) {
+ if (data.length <= 8)
+ throw new IllegalArgumentException("Header too short: " + data.length);
+ if (data[0] != 0 || data[1] != 0)
+ throw new IllegalArgumentException("Not a SOCKS datagram?");
+ if (data[2] != 0)
+ throw new IllegalArgumentException("We can't handle fragments!");
+ int headerlen = 0;
+ int addressType = data[3];
+ if (addressType == 1) {
+ // this will fail in getDestination()
+ headerlen = 6 + 4;
+ } else if (addressType == 3) {
+ headerlen = 6 + 1 + (data[4] & 0xff);
+ } else if (addressType == 4) {
+ // this will fail in getDestination()
+ // but future garlicat partial hash lookup possible?
+ headerlen = 6 + 16;
+ } else {
+ throw new IllegalArgumentException("Unknown address type: " + addressType);
+ }
+ if (data.length < headerlen)
+ throw new IllegalArgumentException("Header too short: " + data.length);
+
+ this.header = new byte[headerlen];
+ System.arraycopy(this.header, 0, data, 0, headerlen);
+ }
+
+ private static final byte[] beg = {0,0,0,3,60};
+ private static final byte[] end = {'.','b','3','2','.','i','2','p',0,0};
+
+ /**
+ * Make a dummy header from a dest,
+ * for those cases where we want to receive unsolicited datagrams.
+ * Unused for now.
+ */
+ public SOCKSHeader(Destination dest) {
+ this.header = new byte[beg.length + 52 + end.length];
+ System.arraycopy(this.header, 0, beg, 0, beg.length);
+ String b32 = Base32.encode(dest.calculateHash().getData());
+ System.arraycopy(this.header, beg.length, b32.getBytes(), 0, 52);
+ System.arraycopy(this.header, beg.length + 52, end, 0, end.length);
+ }
+
+ public String getHost() {
+ int addressType = this.header[3];
+ if (addressType != 3)
+ return null;
+ int namelen = (this.header[4] & 0xff);
+ byte[] nameBytes = new byte[namelen];
+ System.arraycopy(nameBytes, 0, this.header, 5, namelen);
+ return new String(nameBytes);
+ }
+
+ public Destination getDestination() {
+ String name = getHost();
+ if (name == null)
+ return null;
+ try {
+ // the naming service does caching (thankfully)
+ return I2PTunnel.destFromName(name);
+ } catch (DataFormatException dfe) {}
+ return null;
+ }
+
+ public byte[] getBytes() {
+ return header;
+ }
+
+ private byte[] header;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
index 06c3fab552..09e9284deb 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
@@ -9,7 +9,6 @@ package net.i2p.i2ptunnel.socks;
import java.net.Socket;
import net.i2p.client.streaming.I2PSocket;
-import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.Log;
/**
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java
index 67a52d6889..80dfacb6a0 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java
@@ -44,6 +44,10 @@ public class SOCKSServerFactory {
int socksVer = in.readByte();
switch (socksVer) {
+ case 0x04:
+ // SOCKS version 4/4a
+ serv = new SOCKS4aServer(s);
+ break;
case 0x05:
// SOCKS version 5
serv = new SOCKS5Server(s);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java
new file mode 100644
index 0000000000..b56c9082ff
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPPort.java
@@ -0,0 +1,77 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+
+/**
+ * Implements a UDP port and Socks encapsulation / decapsulation.
+ * This is for a single port. If there is demuxing for multiple
+ * ports, it happens outside of here.
+ *
+ * TX:
+ * UDPSource -> SOCKSUDPUnwrapper -> ReplyTracker ( -> I2PSink in SOCKSUDPTunnel)
+ *
+ * RX:
+ * UDPSink <- SOCKSUDPWrapper ( <- MultiSink <- I2PSource in SOCKSUDPTunnel)
+ *
+ * The Unwrapper passes headers to the Wrapper through a cache.
+ * The ReplyTracker passes sinks to MultiSink through a cache.
+ *
+ * @author zzz
+ */
+public class SOCKSUDPPort implements Source, Sink {
+
+ public SOCKSUDPPort(InetAddress host, int port, Map replyMap) {
+
+ // this passes the host and port from UDPUnwrapper to UDPWrapper
+ Map cache = new ConcurrentHashMap(4);
+
+ // rcv from I2P and send to a port
+ this.wrapper = new SOCKSUDPWrapper(cache);
+ this.udpsink = new UDPSink(host, port);
+ this.wrapper.setSink(this.udpsink);
+
+ // rcv from the same port and send to I2P
+ DatagramSocket sock = this.udpsink.getSocket();
+ this.udpsource = new UDPSource(sock);
+ this.unwrapper = new SOCKSUDPUnwrapper(cache);
+ this.udpsource.setSink(this.unwrapper);
+ this.udptracker = new ReplyTracker(this, replyMap);
+ this.unwrapper.setSink(this.udptracker);
+ }
+
+ /** Socks passes this back to the client on the TCP connection */
+ public int getPort() {
+ return this.udpsink.getPort();
+ }
+
+ public void setSink(Sink sink) {
+ this.udptracker.setSink(sink);
+ }
+
+ public void start() {
+ // the other Sources don't use start
+ this.udpsource.start();
+ }
+
+ public void stop() {
+ this.udpsink.stop();
+ this.udpsource.stop();
+ }
+
+ public void send(Destination from, byte[] data) {
+ this.wrapper.send(from, data);
+ }
+
+
+ private UDPSink udpsink;
+ private UDPSource udpsource;
+ private SOCKSUDPWrapper wrapper;
+ private SOCKSUDPUnwrapper unwrapper;
+ private ReplyTracker udptracker;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java
new file mode 100644
index 0000000000..0490a6f0af
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java
@@ -0,0 +1,93 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.net.InetAddress;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.I2PTunnel;
+import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
+
+/**
+ * A Datagram Tunnel that can have multiple bidirectional ports on the UDP side.
+ *
+ * TX:
+ * (ReplyTracker in multiple SOCKSUDPPorts -> ) I2PSink
+ *
+ * RX:
+ * (SOCKSUDPWrapper in multiple SOCKSUDPPorts <- ) MultiSink <- I2PSource
+ *
+ * The reply from a dest goes to the last SOCKSUDPPort that sent to that dest.
+ * If multiple ports are talking to a dest at the same time, this isn't
+ * going to work very well.
+ *
+ * @author zzz modded from streamr/StreamrConsumer
+ */
+public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
+
+ /**
+ * Set up a tunnel with no UDP side yet.
+ * Use add() for each port.
+ */
+ public SOCKSUDPTunnel(I2PTunnel tunnel) {
+ super(null, tunnel, tunnel, tunnel);
+
+ this.ports = new ConcurrentHashMap(1);
+ this.cache = new ConcurrentHashMap(1);
+ this.demuxer = new MultiSink(this.cache);
+ setSink(this.demuxer);
+ }
+
+
+ /** @return the UDP port number */
+ public int add(InetAddress host, int port) {
+ SOCKSUDPPort sup = new SOCKSUDPPort(host, port, this.cache);
+ this.ports.put(Integer.valueOf(sup.getPort()), sup);
+ sup.setSink(this);
+ sup.start();
+ return sup.getPort();
+ }
+
+ public void remove(Integer port) {
+ SOCKSUDPPort sup = this.ports.remove(port);
+ if (sup != null)
+ sup.stop();
+ for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry e = (Map.Entry) iter.next();
+ if (e.getValue() == sup)
+ iter.remove();
+ }
+ }
+
+ @Override
+ public final void startRunning() {
+ super.startRunning();
+ // demuxer start() doesn't do anything
+ startall();
+ }
+
+ @Override
+ public boolean close(boolean forced) {
+ stopall();
+ return super.close(forced);
+ }
+
+ /** you should really add() after startRunning() */
+ private void startall() {
+ }
+
+ private void stopall() {
+ for (SOCKSUDPPort sup : this.ports.values()) {
+ sup.stop();
+ }
+ this.ports.clear();
+ this.cache.clear();
+ }
+
+
+
+ private Map ports;
+ private Map cache;
+ private MultiSink demuxer;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java
new file mode 100644
index 0000000000..2720b6fd47
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java
@@ -0,0 +1,59 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.util.Map;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.Log;
+
+/**
+ * Strip a SOCKS header off a datagram, convert it to a Destination
+ * Ref: RFC 1928
+ *
+ * @author zzz
+ */
+public class SOCKSUDPUnwrapper implements Source, Sink {
+ private static final Log _log = new Log(SOCKSUDPUnwrapper.class);
+
+ /**
+ * @param cache put headers here to pass to SOCKSUDPWrapper
+ */
+ public SOCKSUDPUnwrapper(Map cache) {
+ this.cache = cache;
+ }
+
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {}
+
+ /**
+ *
+ */
+ public void send(Destination ignored_from, byte[] data) {
+ SOCKSHeader h;
+ try {
+ h = new SOCKSHeader(data);
+ } catch (IllegalArgumentException iae) {
+ _log.error(iae.toString());
+ return;
+ }
+ Destination dest = h.getDestination();
+ if (dest == null) {
+ // no, we aren't going to send non-i2p traffic to a UDP outproxy :)
+ _log.error("Destination not found: " + h.getHost());
+ return;
+ }
+
+ cache.put(dest, h);
+
+ int headerlen = h.getBytes().length;
+ byte unwrapped[] = new byte[data.length - headerlen];
+ System.arraycopy(unwrapped, 0, data, headerlen, unwrapped.length);
+ this.sink.send(dest, unwrapped);
+ }
+
+ private Sink sink;
+ private Map cache;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java
new file mode 100644
index 0000000000..4ec8361576
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java
@@ -0,0 +1,49 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.util.Map;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+
+/**
+ * Put a SOCKS header on a datagram
+ * Ref: RFC 1928
+ *
+ * @author zzz
+ */
+public class SOCKSUDPWrapper implements Source, Sink {
+ public SOCKSUDPWrapper(Map cache) {
+ this.cache = cache;
+ }
+
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {}
+
+ /**
+ * Use the cached header, which should have the host string and port
+ *
+ */
+ public void send(Destination from, byte[] data) {
+ if (this.sink == null)
+ return;
+
+ SOCKSHeader h = cache.get(from);
+ if (h == null) {
+ // RFC 1928 says drop
+ // h = new SOCKSHeader(from);
+ return;
+ }
+
+ byte[] header = h.getBytes();
+ byte wrapped[] = new byte[header.length + data.length];
+ System.arraycopy(wrapped, 0, header, 0, header.length);
+ System.arraycopy(wrapped, header.length, data, 0, data.length);
+ this.sink.send(from, wrapped);
+ }
+
+ private Sink sink;
+ private Map cache;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java
new file mode 100644
index 0000000000..5c5a08027e
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/MultiSource.java
@@ -0,0 +1,64 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.streamr;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.List;
+
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+
+/**
+ * Sends to many Sinks
+ * @author welterde
+ * @author zzz modded for I2PTunnel
+ */
+public class MultiSource implements Source, Sink {
+ public MultiSource() {
+ this.sinks = new CopyOnWriteArrayList();
+ }
+
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {}
+
+ public void stop() {
+ this.sinks.clear();
+ }
+
+ public void send(Destination ignored_from, byte[] data) {
+ for(Destination dest : this.sinks) {
+ this.sink.send(dest, data);
+ }
+ }
+
+ public void add(Destination sink) {
+ this.sinks.add(sink);
+ }
+
+ public void remove(Destination sink) {
+ this.sinks.remove(sink);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ private Sink sink;
+ private List sinks;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java
new file mode 100644
index 0000000000..29d1186c4d
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java
@@ -0,0 +1,59 @@
+package net.i2p.i2ptunnel.streamr;
+
+import net.i2p.i2ptunnel.udp.*;
+
+/**
+ *
+ * @author welterde/zzz
+ */
+public class Pinger implements Source, Runnable {
+ public Pinger() {
+ this.thread = new Thread(this);
+ }
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {
+ this.running = true;
+ this.waitlock = new Object();
+ this.thread.start();
+ }
+
+ public void stop() {
+ this.running = false;
+ synchronized(this.waitlock) {
+ this.waitlock.notifyAll();
+ }
+ // send unsubscribe-message
+ byte[] data = new byte[1];
+ data[0] = 1;
+ this.sink.send(null, data);
+ }
+
+ public void run() {
+ // send subscribe-message
+ byte[] data = new byte[1];
+ data[0] = 0;
+ int i = 0;
+ while(this.running) {
+ //System.out.print("p");
+ this.sink.send(null, data);
+ synchronized(this.waitlock) {
+ int delay = 10000;
+ if (i < 5) {
+ i++;
+ delay = 2000;
+ }
+ try {
+ this.waitlock.wait(delay);
+ } catch(InterruptedException ie) {}
+ }
+ }
+ }
+
+ protected Sink sink;
+ protected Thread thread;
+ protected Object waitlock; // should be final and use a factory. LINT
+ protected boolean running;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java
new file mode 100644
index 0000000000..9c1d584ae0
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java
@@ -0,0 +1,67 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.streamr;
+
+import java.net.InetAddress;
+
+import net.i2p.i2ptunnel.I2PTunnel;
+import net.i2p.i2ptunnel.Logging;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
+import net.i2p.util.EventDispatcher;
+
+/**
+ * Compared to a standard I2PTunnel,
+ * this acts like a client on the I2P side (no privkey file)
+ * but a server on the UDP side (sends to a configured host/port)
+ *
+ * @author welterde
+ * @author zzz modded for I2PTunnel
+ */
+public class StreamrConsumer extends I2PTunnelUDPClientBase {
+
+ public StreamrConsumer(InetAddress host, int port, String destination,
+ Logging l, EventDispatcher notifyThis,
+ I2PTunnel tunnel) {
+ super(destination, l, notifyThis, tunnel);
+
+ // create udp-destination
+ this.sink = new UDPSink(host, port);
+ setSink(this.sink);
+
+ // create pinger
+ this.pinger = new Pinger();
+ this.pinger.setSink(this);
+ }
+
+ @Override
+ public final void startRunning() {
+ super.startRunning();
+ // send subscribe-message
+ this.pinger.start();
+ l.log("Streamr client ready");
+ }
+
+ @Override
+ public boolean close(boolean forced) {
+ // send unsubscribe-message
+ this.pinger.stop();
+ this.sink.stop();
+ return super.close(forced);
+ }
+
+
+
+
+
+
+
+
+
+
+ private UDPSink sink;
+ private Pinger pinger;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java
new file mode 100644
index 0000000000..7d6b14491d
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java
@@ -0,0 +1,73 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.streamr;
+
+// system
+import java.io.File;
+
+// i2p
+import net.i2p.i2ptunnel.I2PTunnel;
+import net.i2p.i2ptunnel.Logging;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPServerBase;
+import net.i2p.util.EventDispatcher;
+
+/**
+ * Compared to a standard I2PTunnel,
+ * this acts like a server on the I2P side (persistent privkey file)
+ * but a client on the UDP side (receives on a configured port)
+ *
+ * @author welterde
+ * @author zzz modded for I2PTunnel
+ */
+public class StreamrProducer extends I2PTunnelUDPServerBase {
+
+ public StreamrProducer(int port,
+ File privkey, String privkeyname, Logging l,
+ EventDispatcher notifyThis, I2PTunnel tunnel) {
+ // verify subscription requests
+ super(true, privkey, privkeyname, l, notifyThis, tunnel);
+
+ // The broadcaster
+ this.multi = new MultiSource();
+ this.multi.setSink(this);
+
+ // The listener
+ this.subscriber = new Subscriber(this.multi);
+ setSink(this.subscriber);
+
+ // now start udp-server
+ this.server = new UDPSource(port);
+ this.server.setSink(this.multi);
+ }
+
+ @Override
+ public final void startRunning() {
+ super.startRunning();
+ this.server.start();
+ l.log("Streamr server ready");
+ }
+
+ @Override
+ public boolean close(boolean forced) {
+ this.server.stop();
+ this.multi.stop();
+ return super.close(forced);
+ }
+
+
+
+
+
+
+
+
+
+
+ private MultiSource multi;
+ private UDPSource server;
+ private Sink subscriber;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java
new file mode 100644
index 0000000000..377292ddef
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java
@@ -0,0 +1,70 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.streamr;
+
+// system
+import java.util.Set;
+
+// i2p
+import net.i2p.client.I2PSession;
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.ConcurrentHashSet;
+
+/**
+ * server-mode
+ * @author welterde
+ * @author zzz modded from Producer for I2PTunnel
+ */
+public class Subscriber implements Sink {
+
+ public Subscriber(MultiSource multi) {
+ this.multi = multi;
+ // subscriptions
+ this.subscriptions = new ConcurrentHashSet();
+ }
+
+ public void send(Destination dest, byte[] data) {
+ if(dest == null || data.length < 1) {
+ // invalid packet
+ // TODO: write to log
+ } else {
+ byte ctrl = data[0];
+ if(ctrl == 0) {
+ if (!this.subscriptions.contains(dest)) {
+ // subscribe
+ System.out.println("Add subscription: " + dest.toBase64().substring(0,4));
+ this.subscriptions.add(dest);
+ this.multi.add(dest);
+ } // else already subscribed
+ } else if(ctrl == 1) {
+ // unsubscribe
+ System.out.println("Remove subscription: " + dest.toBase64().substring(0,4));
+ boolean removed = this.subscriptions.remove(dest);
+ if(removed)
+ multi.remove(dest);
+ } else {
+ // invalid packet
+ // TODO: write to log
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+ private I2PSession sess;
+ private Source listener;
+ private Set subscriptions;
+ private MultiSource multi;
+ private Source server;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java
new file mode 100644
index 0000000000..e08596af11
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java
@@ -0,0 +1,73 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+// i2p
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.Destination;
+import net.i2p.client.datagram.I2PDatagramMaker;
+
+/**
+ * Producer
+ *
+ * This sends to a fixed destination specified in the constructor
+ *
+ * @author welterde
+ */
+public class I2PSink implements Sink {
+ public I2PSink(I2PSession sess, Destination dest) {
+ this(sess, dest, false);
+ }
+ public I2PSink(I2PSession sess, Destination dest, boolean raw) {
+ this.sess = sess;
+ this.dest = dest;
+ this.raw = raw;
+
+ // create maker
+ if (!raw)
+ this.maker = new I2PDatagramMaker(this.sess);
+ }
+
+ /** @param src ignored */
+ public synchronized void send(Destination src, byte[] data) {
+ //System.out.print("w");
+ // create payload
+ byte[] payload;
+ if(!this.raw) {
+ synchronized(this.maker) {
+ payload = this.maker.makeI2PDatagram(data);
+ }
+ } else
+ payload = data;
+
+ // send message
+ try {
+ this.sess.sendMessage(this.dest, payload, I2PSession.PROTO_DATAGRAM,
+ I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
+ } catch(I2PSessionException exc) {
+ // TODO: handle better
+ exc.printStackTrace();
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ protected boolean raw;
+ protected I2PSession sess;
+ protected Destination dest;
+ protected I2PDatagramMaker maker; // should be final and use a factory. LINT
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java
new file mode 100644
index 0000000000..2da942a74a
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java
@@ -0,0 +1,71 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+// i2p
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.Destination;
+import net.i2p.client.datagram.I2PDatagramMaker;
+
+/**
+ * Producer
+ *
+ * This sends to any destination specified in send()
+ *
+ * @author zzz modded from I2PSink by welterde
+ */
+public class I2PSinkAnywhere implements Sink {
+ public I2PSinkAnywhere(I2PSession sess) {
+ this(sess, false);
+ }
+ public I2PSinkAnywhere(I2PSession sess, boolean raw) {
+ this.sess = sess;
+ this.raw = raw;
+
+ // create maker
+ if (!raw)
+ this.maker = new I2PDatagramMaker(this.sess);
+ }
+
+ /** @param to - where it's going */
+ public synchronized void send(Destination to, byte[] data) {
+ // create payload
+ byte[] payload;
+ if(!this.raw) {
+ synchronized(this.maker) {
+ payload = this.maker.makeI2PDatagram(data);
+ }
+ } else
+ payload = data;
+
+ // send message
+ try {
+ this.sess.sendMessage(to, payload, I2PSession.PROTO_DATAGRAM,
+ I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
+ } catch(I2PSessionException exc) {
+ // TODO: handle better
+ exc.printStackTrace();
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ protected boolean raw;
+ protected I2PSession sess;
+ protected Destination dest;
+ protected I2PDatagramMaker maker; // should be final and use a factory. LINT
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
new file mode 100644
index 0000000000..0b54747772
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
@@ -0,0 +1,123 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+// system
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+// i2p
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionListener;
+import net.i2p.client.datagram.I2PDatagramDissector;
+
+/**
+ *
+ * @author welterde
+ */
+public class I2PSource implements Source, Runnable {
+ public I2PSource(I2PSession sess) {
+ this(sess, true, false);
+ }
+ public I2PSource(I2PSession sess, boolean verify) {
+ this(sess, verify, false);
+ }
+ public I2PSource(I2PSession sess, boolean verify, boolean raw) {
+ this.sess = sess;
+ this.sink = null;
+ this.verify = verify;
+ this.raw = raw;
+
+ // create queue
+ this.queue = new ArrayBlockingQueue(256);
+
+ // create listener
+ this.sess.setSessionListener(new Listener());
+
+ // create thread
+ this.thread = new Thread(this);
+ }
+
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {
+ this.thread.start();
+ }
+
+ public void run() {
+ // create dissector
+ I2PDatagramDissector diss = new I2PDatagramDissector();
+ while(true) {
+ try {
+ // get id
+ int id = this.queue.take();
+
+ // receive message
+ byte[] msg = this.sess.receiveMessage(id);
+
+ if(!this.raw) {
+ // load datagram into it
+ diss.loadI2PDatagram(msg);
+
+ // now call sink
+ if(this.verify)
+ this.sink.send(diss.getSender(), diss.getPayload());
+ else
+ this.sink.send(diss.extractSender(), diss.extractPayload());
+ } else {
+ // verify is ignored
+ this.sink.send(null, msg);
+ }
+ //System.out.print("r");
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+
+
+
+
+ protected class Listener implements I2PSessionListener {
+
+ public void messageAvailable(I2PSession sess, int id, long size) {
+ try {
+ queue.put(id);
+ } catch(Exception e) {
+ // ignore
+ }
+ }
+
+ public void reportAbuse(I2PSession arg0, int arg1) {
+ // ignore
+ }
+
+ public void disconnected(I2PSession arg0) {
+ // ignore
+ }
+
+ public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
+ // ignore
+ }
+
+ }
+
+
+
+
+
+
+ protected I2PSession sess;
+ protected BlockingQueue queue;
+ protected Sink sink;
+ protected Thread thread;
+ protected boolean verify;
+ protected boolean raw;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java
new file mode 100644
index 0000000000..49e3e47a3e
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java
@@ -0,0 +1,17 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+// i2p
+import net.i2p.data.Destination;
+
+/**
+ *
+ * @author welterde
+ */
+public interface Sink {
+ public void send(Destination src, byte[] data);
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java
new file mode 100644
index 0000000000..f65d03b196
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java
@@ -0,0 +1,15 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+/**
+ *
+ * @author welterde
+ */
+public interface Source {
+ public void setSink(Sink sink);
+ public void start();
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java
new file mode 100644
index 0000000000..b8b57e696c
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java
@@ -0,0 +1,15 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+/**
+ *
+ * @author welterde
+ */
+public interface Stream {
+ public void start();
+ public void stop();
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java
new file mode 100644
index 0000000000..d2e8e89247
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java
@@ -0,0 +1,77 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+// system
+import java.net.DatagramSocket;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+
+// i2p
+import net.i2p.data.Destination;
+
+/**
+ *
+ * @author welterde
+ */
+public class UDPSink implements Sink {
+ public UDPSink(InetAddress host, int port) {
+ // create socket
+ try {
+ this.sock = new DatagramSocket();
+ } catch(Exception e) {
+ // TODO: fail better
+ throw new RuntimeException("failed to open udp-socket", e);
+ }
+
+ this.remoteHost = host;
+
+ // remote port
+ this.remotePort = port;
+ }
+
+ public void send(Destination src, byte[] data) {
+ // if data.length > this.sock.getSendBufferSize() ...
+
+ // create packet
+ DatagramPacket packet = new DatagramPacket(data, data.length, this.remoteHost, this.remotePort);
+
+ // send packet
+ try {
+ this.sock.send(packet);
+ } catch(Exception e) {
+ // TODO: fail a bit better
+ e.printStackTrace();
+ }
+ }
+
+ public int getPort() {
+ return this.sock.getLocalPort();
+ }
+
+ /** to pass to UDPSource constructor */
+ public DatagramSocket getSocket() {
+ return this.sock;
+ }
+
+ public void stop() {
+ this.sock.close();
+ }
+
+
+
+
+
+
+
+
+
+
+ protected DatagramSocket sock;
+ protected InetAddress remoteHost;
+ protected int remotePort;
+
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
new file mode 100644
index 0000000000..fc1dd5bf22
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
@@ -0,0 +1,91 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package net.i2p.i2ptunnel.udp;
+
+// system
+import java.net.DatagramSocket;
+import java.net.DatagramPacket;
+
+/**
+ *
+ * @author welterde
+ */
+public class UDPSource implements Source, Runnable {
+ public static final int MAX_SIZE = 15360;
+ public UDPSource(int port) {
+ this.sink = null;
+
+ // create udp-socket
+ try {
+ this.sock = new DatagramSocket(port);
+ } catch(Exception e) {
+ throw new RuntimeException("failed to listen...", e);
+ }
+
+ // create thread
+ this.thread = new Thread(this);
+ }
+
+ /** use socket from UDPSink */
+ public UDPSource(DatagramSocket sock) {
+ this.sink = null;
+ this.sock = sock;
+ this.thread = new Thread(this);
+ }
+
+ public void setSink(Sink sink) {
+ this.sink = sink;
+ }
+
+ public void start() {
+ this.thread.start();
+ }
+
+ public void run() {
+ // create packet
+ byte[] buf = new byte[MAX_SIZE];
+ DatagramPacket pack = new DatagramPacket(buf, buf.length);
+ while(true) {
+ try {
+ // receive...
+ this.sock.receive(pack);
+
+ // create new data array
+ byte[] nbuf = new byte[pack.getLength()];
+
+ // copy over
+ System.arraycopy(pack.getData(), 0, nbuf, 0, nbuf.length);
+
+ // transfer to sink
+ this.sink.send(null, nbuf);
+ //System.out.print("i");
+ } catch(Exception e) {
+ e.printStackTrace();
+ break;
+ }
+ }
+ }
+
+ public void stop() {
+ this.sock.close();
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ protected DatagramSocket sock;
+ protected Sink sink;
+ protected Thread thread;
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java
new file mode 100644
index 0000000000..14945c8422
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java
@@ -0,0 +1,197 @@
+/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
+ * (c) 2003 - 2004 mihi
+ */
+package net.i2p.i2ptunnel.udpTunnel;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.net.ServerSocket;
+
+import net.i2p.I2PAppContext;
+import net.i2p.client.I2PClient;
+import net.i2p.client.I2PClientFactory;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.I2PTunnel;
+import net.i2p.i2ptunnel.I2PTunnelTask;
+import net.i2p.i2ptunnel.Logging;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.EventDispatcher;
+import net.i2p.util.Log;
+
+ /**
+ * Base client class that sets up an I2P Datagram client destination.
+ * The UDP side is not implemented here, as there are at least
+ * two possibilities:
+ *
+ * 1) UDP side is a "server"
+ * Example: Streamr Consumer
+ * - Configure a destination host and port
+ * - External application sends no data
+ * - Extending class must have a constructor with host and port arguments
+ *
+ * 2) UDP side is a client/server
+ * Example: SOCKS UDP (DNS requests?)
+ * - configure an inbound port and a destination host and port
+ * - External application sends and receives data
+ * - Extending class must have a constructor with host and 2 port arguments
+ *
+ * So the implementing class must create a UDPSource and/or UDPSink,
+ * and must call setSink().
+ *
+ * @author zzz with portions from welterde's streamr
+ */
+ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink {
+
+ private static final Log _log = new Log(I2PTunnelUDPClientBase.class);
+ protected I2PAppContext _context;
+ protected Logging l;
+
+ static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
+
+ private static volatile long __clientId = 0;
+ protected long _clientId;
+
+ protected Destination dest = null;
+
+ private boolean listenerReady = false;
+
+ private ServerSocket ss;
+
+ private Object startLock = new Object();
+ private boolean startRunning = false;
+
+ private byte[] pubkey;
+
+ private String handlerName;
+
+ private Object conLock = new Object();
+
+ /** How many connections will we allow to be in the process of being built at once? */
+ private int _numConnectionBuilders;
+ /** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
+ private int _maxWaitTime;
+
+ private I2PSession _session;
+ private Source _i2pSource;
+ private Sink _i2pSink;
+ private Destination _otherDest;
+ /**
+ * @throws IllegalArgumentException if the I2CP configuration is b0rked so
+ * badly that we cant create a socketManager
+ */
+ public I2PTunnelUDPClientBase(String destination, Logging l, EventDispatcher notifyThis,
+ I2PTunnel tunnel) throws IllegalArgumentException {
+ super("UDPServer", notifyThis, tunnel);
+ _clientId = ++__clientId;
+ this.l = l;
+
+ _context = tunnel.getContext();
+
+ tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
+
+ // create i2pclient and destination
+ I2PClient client = I2PClientFactory.createClient();
+ Destination destN;
+ byte[] key;
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(512);
+ destN = client.createDestination(out);
+ key = out.toByteArray();
+ } catch(Exception exc) {
+ throw new RuntimeException("failed to create i2p-destination", exc);
+ }
+
+ // create a session
+ try {
+ ByteArrayInputStream in = new ByteArrayInputStream(key);
+ _session = client.createSession(in, tunnel.getClientOptions());
+ } catch(Exception exc) {
+ throw new RuntimeException("failed to create session", exc);
+ }
+
+ // Setup the source. Always expect raw unverified datagrams.
+ _i2pSource = new I2PSource(_session, false, true);
+
+ // Setup the sink. Always send repliable datagrams.
+ if (destination != null && destination.length() > 0) {
+ try {
+ _otherDest = I2PTunnel.destFromName(destination);
+ } catch (DataFormatException dfe) {}
+ if (_otherDest == null) {
+ l.log("Could not resolve " + destination);
+ throw new RuntimeException("failed to create session - could not resolve " + destination);
+ }
+ _i2pSink = new I2PSink(_session, _otherDest, false);
+ } else {
+ _i2pSink = new I2PSinkAnywhere(_session, false);
+ }
+ }
+
+ /**
+ * Actually start working on outgoing connections.
+ * Classes should override to start UDP side as well.
+ *
+ * Not specified in I2PTunnelTask but used in both
+ * I2PTunnelClientBase and I2PTunnelServer so let's
+ * implement it here too.
+ */
+ public void startRunning() {
+ synchronized (startLock) {
+ try {
+ _session.connect();
+ } catch(I2PSessionException exc) {
+ throw new RuntimeException("failed to connect session", exc);
+ }
+ start();
+ startRunning = true;
+ startLock.notify();
+ }
+ open = true;
+ }
+
+ /**
+ * I2PTunnelTask Methods
+ *
+ * Classes should override to close UDP side as well
+ */
+ public boolean close(boolean forced) {
+ if (!open) return true;
+ if (_session != null) {
+ try {
+ _session.destroySession();
+ } catch (I2PSessionException ise) {}
+ }
+ l.log("Closing client " + toString());
+ open = false;
+ return true;
+ }
+
+ /**
+ * Source Methods
+ *
+ * Sets the receiver of the UDP datagrams from I2P
+ * Subclass must call this after constructor
+ * and before start()
+ */
+ public void setSink(Sink s) {
+ _i2pSource.setSink(s);
+ }
+
+ /** start the source */
+ public void start() {
+ _i2pSource.start();
+ }
+
+ /**
+ * Sink Methods
+ *
+ * @param to - ignored if configured for a single destination
+ * (we use the dest specified in the constructor)
+ */
+ public void send(Destination to, byte[] data) {
+ _i2pSink.send(to, data);
+ }
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java
new file mode 100644
index 0000000000..6ba8379f94
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java
@@ -0,0 +1,204 @@
+/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
+ * (c) 2003 - 2004 mihi
+ */
+package net.i2p.i2ptunnel.udpTunnel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.i2p.I2PException;
+import net.i2p.client.I2PClient;
+import net.i2p.client.I2PClientFactory;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.I2PTunnel;
+import net.i2p.i2ptunnel.I2PTunnelTask;
+import net.i2p.i2ptunnel.Logging;
+import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.EventDispatcher;
+import net.i2p.util.Log;
+
+ /**
+ * Base client class that sets up an I2P Datagram server destination.
+ * The UDP side is not implemented here, as there are at least
+ * two possibilities:
+ *
+ * 1) UDP side is a "client"
+ * Example: Streamr Producer
+ * - configure an inbound port
+ * - External application receives no data
+ * - Extending class must have a constructor with a port argument
+ *
+ * 2) UDP side is a client/server
+ * Example: DNS
+ * - configure an inbound port and a destination host and port
+ * - External application sends and receives data
+ * - Extending class must have a constructor with host and 2 port arguments
+ *
+ * So the implementing class must create a UDPSource and/or UDPSink,
+ * and must call setSink().
+ *
+ * @author zzz with portions from welterde's streamr
+ */
+
+public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink {
+
+ private final static Log _log = new Log(I2PTunnelUDPServerBase.class);
+
+ private final Object lock = new Object();
+ protected Object slock = new Object();
+
+ private static volatile long __serverId = 0;
+
+ protected Logging l;
+
+ private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
+ /** default timeout to 3 minutes - override if desired */
+ protected long readTimeout = DEFAULT_READ_TIMEOUT;
+
+ private I2PSession _session;
+ private Source _i2pSource;
+ private Sink _i2pSink;
+
+ /**
+ *
+ * @throws IllegalArgumentException if the I2CP configuration is b0rked so
+ * badly that we cant create a socketManager
+ *
+ */
+
+ public I2PTunnelUDPServerBase(boolean verify, File privkey, String privkeyname, Logging l,
+ EventDispatcher notifyThis, I2PTunnel tunnel) {
+ super("UDPServer <- " + privkeyname, notifyThis, tunnel);
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(privkey);
+ init(verify, fis, privkeyname, l);
+ } catch (IOException ioe) {
+ _log.error("Error starting server", ioe);
+ notifyEvent("openServerResult", "error");
+ } finally {
+ if (fis != null)
+ try { fis.close(); } catch (IOException ioe) {}
+ }
+ }
+
+ private void init(boolean verify, InputStream privData, String privkeyname, Logging l) {
+ this.l = l;
+ int portNum = 7654;
+ if (getTunnel().port != null) {
+ try {
+ portNum = Integer.parseInt(getTunnel().port);
+ } catch (NumberFormatException nfe) {
+ _log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
+ }
+ }
+
+ // create i2pclient
+ I2PClient client = I2PClientFactory.createClient();
+
+ try {
+ _session = client.createSession(privData, getTunnel().getClientOptions());
+ } catch(I2PSessionException exc) {
+ throw new RuntimeException("failed to create session", exc);
+ }
+
+ // Setup the source. Always expect repliable datagrams, optionally verify
+ _i2pSource = new I2PSource(_session, verify, false);
+
+ // Setup the sink. Always send raw datagrams.
+ _i2pSink = new I2PSinkAnywhere(_session, true);
+ }
+
+ /**
+ * Classes should override to start UDP side as well.
+ *
+ * Not specified in I2PTunnelTask but used in both
+ * I2PTunnelClientBase and I2PTunnelServer so let's
+ * implement it here too.
+ */
+ public void startRunning() {
+ //synchronized (startLock) {
+ try {
+ _session.connect();
+ } catch(I2PSessionException exc) {
+ throw new RuntimeException("failed to connect session", exc);
+ }
+ start();
+ //}
+
+ notifyEvent("openServerResult", "ok");
+ open = true;
+ }
+
+ /**
+ * Set the read idle timeout for newly-created connections (in
+ * milliseconds). After this time expires without data being reached from
+ * the I2P network, the connection itself will be closed.
+ */
+ public void setReadTimeout(long ms) {
+ readTimeout = ms;
+ }
+
+ /**
+ * Get the read idle timeout for newly-created connections (in
+ * milliseconds).
+ *
+ * @return The read timeout used for connections
+ */
+ public long getReadTimeout() {
+ return readTimeout;
+ }
+
+ /**
+ * I2PTunnelTask Methods
+ *
+ * Classes should override to close UDP side as well
+ */
+ public boolean close(boolean forced) {
+ if (!open) return true;
+ synchronized (lock) {
+ l.log("Shutting down server " + toString());
+ try {
+ if (_session != null) {
+ _session.destroySession();
+ }
+ } catch (I2PException ex) {
+ _log.error("Error destroying the session", ex);
+ }
+ l.log("Server shut down.");
+ open = false;
+ return true;
+ }
+ }
+
+ /**
+ * Source Methods
+ *
+ * Sets the receiver of the UDP datagrams from I2P
+ * Subclass must call this after constructor
+ * and before start()
+ */
+ public void setSink(Sink s) {
+ _i2pSource.setSink(s);
+ }
+
+ /** start the source */
+ public void start() {
+ _i2pSource.start();
+ }
+
+ /**
+ * Sink Methods
+ *
+ * @param to
+ *
+ */
+ public void send(Destination to, byte[] data) {
+ _i2pSink.send(to, data);
+ }
+}
+
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
index e62bd25004..a029de725a 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
@@ -8,7 +8,6 @@ package net.i2p.i2ptunnel.web;
*
*/
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
@@ -60,8 +59,9 @@ public class EditBean extends IndexBean {
TunnelController tun = getController(tunnel);
if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile();
- else
- return "";
+ if (tunnel < 0)
+ tunnel = _group.getControllers().size();
+ return "i2ptunnel" + tunnel + "-privKeys.dat";
}
public boolean startAutomatically(int tunnel) {
@@ -156,6 +156,14 @@ public class EditBean extends IndexBean {
return getBooleanProperty(tunnel, "i2cp.newDestOnResume");
}
+ public boolean getPersistentClientKey(int tunnel) {
+ return getBooleanProperty(tunnel, "persistentClientKey");
+ }
+
+ public boolean getDelayOpen(int tunnel) {
+ return getBooleanProperty(tunnel, "i2cp.delayOpen");
+ }
+
private int getProperty(int tunnel, String prop, int def) {
TunnelController tun = getController(tunnel);
if (tun != null) {
@@ -197,7 +205,7 @@ public class EditBean extends IndexBean {
if (tun != null)
return tun.getI2CPHost();
else
- return "localhost";
+ return "127.0.0.1";
}
public String getI2CPPort(int tunnel) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
index 46b5557729..ce547cd029 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
@@ -142,7 +142,7 @@ public class IndexBean {
}
private String processAction() {
- if ( (_action == null) || (_action.trim().length() <= 0) )
+ if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action)))
return "";
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
return "Invalid nonce, are you being spoofed?";
@@ -351,6 +351,7 @@ public class IndexBean {
("httpclient".equals(type)) ||
("sockstunnel".equals(type)) ||
("connectclient".equals(type)) ||
+ ("streamrclient".equals(type)) ||
("ircclient".equals(type)));
}
@@ -384,8 +385,11 @@ public class IndexBean {
else if ("ircclient".equals(internalType)) return "IRC client";
else if ("server".equals(internalType)) return "Standard server";
else if ("httpserver".equals(internalType)) return "HTTP server";
- else if ("sockstunnel".equals(internalType)) return "SOCKS 5 proxy";
+ else if ("sockstunnel".equals(internalType)) return "SOCKS 4/4a/5 proxy";
else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy";
+ else if ("ircserver".equals(internalType)) return "IRC server";
+ else if ("streamrclient".equals(internalType)) return "Streamr client";
+ else if ("streamrserver".equals(internalType)) return "Streamr server";
else return internalType;
}
@@ -433,7 +437,8 @@ public class IndexBean {
TunnelController tun = getController(tunnel);
if (tun == null) return "";
String rv;
- if ("client".equals(tun.getType())||"ircclient".equals(tun.getType()))
+ if ("client".equals(tun.getType()) || "ircclient".equals(tun.getType()) ||
+ "streamrclient".equals(tun.getType()))
rv = tun.getTargetDestination();
else
rv = tun.getProxyList();
@@ -609,8 +614,14 @@ public class IndexBean {
public void setAccess(String moo) {
_booleanOptions.add("i2cp.enableAccessList");
}
- public void setNewDest(String moo) {
- _booleanOptions.add("i2cp.newDestOnResume");
+ public void setDelayOpen(String moo) {
+ _booleanOptions.add("i2cp.delayOpen");
+ }
+ public void setNewDest(String val) {
+ if ("1".equals(val))
+ _booleanOptions.add("i2cp.newDestOnResume");
+ else if ("2".equals(val))
+ _booleanOptions.add("persistentClientKey");
}
public void setReduceTime(String val) {
@@ -785,8 +796,6 @@ public class IndexBean {
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
- if (_privKeyFile != null)
- config.setProperty("privKeyFile", _privKeyFile);
for (String p : _booleanServerOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
for (String p : _otherServerOpts)
@@ -797,7 +806,7 @@ public class IndexBean {
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
- } else if ("ircclient".equals(_type) || "client".equals(_type)) {
+ } else if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
} else if ("httpserver".equals(_type)) {
@@ -814,7 +823,7 @@ public class IndexBean {
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize"
};
private static final String _booleanClientOpts[] = {
- "i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume"
+ "i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
};
private static final String _booleanServerOpts[] = {
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", "i2cp.enableAccessList"
@@ -847,6 +856,8 @@ public class IndexBean {
} else {
config.setProperty("i2cpPort", "7654");
}
+ if (_privKeyFile != null)
+ config.setProperty("privKeyFile", _privKeyFile);
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
diff --git a/apps/i2ptunnel/jsp/edit.jsp b/apps/i2ptunnel/jsp/edit.jsp
index 67fdf016c3..b58798b202 100644
--- a/apps/i2ptunnel/jsp/edit.jsp
+++ b/apps/i2ptunnel/jsp/edit.jsp
@@ -16,10 +16,8 @@ String tun = request.getParameter("tunnel");
int curTunnel = -1;
if (EditBean.isClient(type)) {
%><%
- } else if ("server".equals(type) || "httpserver".equals(type)) {
- %><%
} else {
- %>Invalid tunnel type<%
+ %><%
}
}
%>
diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp
index 3e4c3ecd80..915da5db96 100644
--- a/apps/i2ptunnel/jsp/editClient.jsp
+++ b/apps/i2ptunnel/jsp/editClient.jsp
@@ -75,7 +75,11 @@
\n");
}
}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java
index 09f0905bf3..b43bc4d1f1 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java
@@ -2,9 +2,9 @@ package net.i2p.router.web;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
-import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
+import net.i2p.util.ConvertToHash;
/**
* Support additions via B64 Destkey, B64 Desthash, or blahblah.i2p
@@ -19,27 +19,12 @@ public class ConfigKeyringHandler extends FormHandler {
addFormError("You must enter a destination and a key");
return;
}
- Hash h = new Hash();
- try {
- h.fromBase64(_peer);
- } catch (DataFormatException dfe) {}
- if (h.getData() == null) {
- try {
- Destination d = new Destination();
- d.fromBase64(_peer);
- h = d.calculateHash();
- } catch (DataFormatException dfe) {}
- }
- if (h.getData() == null) {
- Destination d = _context.namingService().lookup(_peer);
- if (d != null)
- h = d.calculateHash();
- }
+ Hash h = ConvertToHash.getHash(_peer);
SessionKey sk = new SessionKey();
try {
sk.fromBase64(_key);
} catch (DataFormatException dfe) {}
- if (h.getData() != null && sk.getData() != null) {
+ if (h != null && h.getData() != null && sk.getData() != null) {
_context.keyRing().put(h, sk);
addFormNotice("Key for " + h.toBase64() + " added to keyring");
} else {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
index 0ddcd58a9d..a4fe7483ec 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
@@ -237,7 +237,7 @@ public class ConfigNetHandler extends FormHandler {
private void hiddenSwitch() {
// Full restart required to generate new keys
- _context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
+ _context.addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
index 3c9fe1bbf4..7ae7181bc2 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
@@ -24,19 +24,22 @@ public class ConfigRestartBean {
RouterContext ctx = ContextHelper.getContext(null);
String systemNonce = getNonce();
if ( (nonce != null) && (systemNonce.equals(nonce)) && (action != null) ) {
- if ("shutdownImmediate".equals(action)) {
- ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD));
- ctx.router().shutdown(Router.EXIT_HARD); // never returns
- } else if ("cancelShutdown".equals(action)) {
+ // Normal browsers send value, IE sends button label
+ if ("shutdownImmediate".equals(action) || "Shutdown immediately".equals(action)) {
+ ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD));
+ //ctx.router().shutdown(Router.EXIT_HARD); // never returns
+ ctx.router().shutdownGracefully(Router.EXIT_HARD); // give the UI time to respond
+ } else if ("cancelShutdown".equals(action) || "Cancel shutdown".equals(action)) {
ctx.router().cancelGracefulShutdown();
- } else if ("restartImmediate".equals(action)) {
- ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
- ctx.router().shutdown(Router.EXIT_HARD_RESTART); // never returns
- } else if ("restart".equals(action)) {
- ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
+ } else if ("restartImmediate".equals(action) || "Restart immediately".equals(action)) {
+ ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
+ //ctx.router().shutdown(Router.EXIT_HARD_RESTART); // never returns
+ ctx.router().shutdownGracefully(Router.EXIT_HARD_RESTART); // give the UI time to respond
+ } else if ("restart".equalsIgnoreCase(action)) {
+ ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
ctx.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
- } else if ("shutdown".equals(action)) {
- ctx.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
+ } else if ("shutdown".equalsIgnoreCase(action)) {
+ ctx.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
ctx.router().shutdownGracefully();
}
}
@@ -79,9 +82,18 @@ public class ConfigRestartBean {
}
private static boolean isShuttingDown(RouterContext ctx) {
- return Router.EXIT_GRACEFUL == ctx.router().scheduledGracefulExitCode();
+ return Router.EXIT_GRACEFUL == ctx.router().scheduledGracefulExitCode() ||
+ Router.EXIT_HARD == ctx.router().scheduledGracefulExitCode();
}
private static boolean isRestarting(RouterContext ctx) {
- return Router.EXIT_GRACEFUL_RESTART == ctx.router().scheduledGracefulExitCode();
+ return Router.EXIT_GRACEFUL_RESTART == ctx.router().scheduledGracefulExitCode() ||
+ Router.EXIT_HARD_RESTART == ctx.router().scheduledGracefulExitCode();
+ }
+ /** this is for summaryframe.jsp */
+ public static long getRestartTimeRemaining() {
+ RouterContext ctx = ContextHelper.getContext(null);
+ if (ctx.router().gracefulShutdownInProgress())
+ return ctx.router().getShutdownTimeRemaining();
+ return Long.MAX_VALUE/2; // summaryframe.jsp adds a safety factor so we don't want to overflow...
}
}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
index bd3bf7a5ea..0dc4d1e62b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
@@ -53,31 +53,31 @@ public class ConfigServiceHandler extends FormHandler {
if (_action == null) return;
if ("Shutdown gracefully".equals(_action)) {
- _context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
+ _context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
_context.router().shutdownGracefully();
addFormNotice("Graceful shutdown initiated");
} else if ("Shutdown immediately".equals(_action)) {
- _context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD));
+ _context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD));
_context.router().shutdown(Router.EXIT_HARD);
addFormNotice("Shutdown immediately! boom bye bye bad bwoy");
} else if ("Cancel graceful shutdown".equals(_action)) {
_context.router().cancelGracefulShutdown();
addFormNotice("Graceful shutdown cancelled");
} else if ("Graceful restart".equals(_action)) {
- _context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
+ _context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
addFormNotice("Graceful restart requested");
} else if ("Hard restart".equals(_action)) {
- _context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
+ _context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
_context.router().shutdown(Router.EXIT_HARD_RESTART);
addFormNotice("Hard restart requested");
} else if ("Rekey and Restart".equals(_action)) {
addFormNotice("Rekeying after graceful restart");
- _context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
+ _context.addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
} else if ("Rekey and Shutdown".equals(_action)) {
addFormNotice("Rekeying after graceful shutdown");
- _context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
+ _context.addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL);
} else if ("Run I2P on startup".equals(_action)) {
installService();
@@ -155,7 +155,7 @@ public class ConfigServiceHandler extends FormHandler {
}
// releases <= 0.6.5 deleted the entry completely
if (shouldLaunchBrowser && !found) {
- ClientAppConfig ca = new ClientAppConfig(UrlLauncher.class.getName(), "consoleBrowser", "http://localhost:7657", 5, false);
+ ClientAppConfig ca = new ClientAppConfig(UrlLauncher.class.getName(), "consoleBrowser", "http://127.0.0.1:7657", 5, false);
clients.add(ca);
}
ClientAppConfig.writeClientAppConfig(_context, clients);
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
index 818b748a7b..1729a209be 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
@@ -21,13 +21,13 @@ public class ConfigUpdateHandler extends FormHandler {
// public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
public static final String DEFAULT_NEWS_URL = "http://complication.i2p/news.xml";
public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
- public static final String DEFAULT_REFRESH_FREQUENCY = 12*60*60*1000 + "";
+ public static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
public static final String PROP_UPDATE_POLICY = "router.updatePolicy";
- public static final String DEFAULT_UPDATE_POLICY = "notify";
+ public static final String DEFAULT_UPDATE_POLICY = "download";
public static final String PROP_SHOULD_PROXY = "router.updateThroughProxy";
public static final String DEFAULT_SHOULD_PROXY = Boolean.TRUE.toString();
public static final String PROP_PROXY_HOST = "router.updateProxyHost";
- public static final String DEFAULT_PROXY_HOST = "localhost";
+ public static final String DEFAULT_PROXY_HOST = "127.0.0.1";
public static final String PROP_PROXY_PORT = "router.updateProxyPort";
public static final String DEFAULT_PROXY_PORT = "4444";
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
index ae11e55fbf..862902b1f8 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
@@ -5,6 +5,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
+import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.apps.systray.SysTray;
@@ -36,6 +37,14 @@ public class RouterConsoleRunner {
System.setProperty("java.awt.headless", "true");
}
+ /**
+ * @param args second arg may be a comma-separated list of bind addresses,
+ * for example ::1,127.0.0.1
+ * On XP, the other order (127.0.0.1,::1) fails the IPV6 bind,
+ * because 127.0.0.1 will bind ::1 also. But even though it's bound
+ * to both, we can't connect to [::1]:7657 for some reason.
+ * So the wise choice is ::1,127.0.0.1
+ */
public RouterConsoleRunner(String args[]) {
if (args.length == 3) {
_listenPort = args[0].trim();
@@ -66,7 +75,24 @@ public class RouterConsoleRunner {
rewrite = true;
}
try {
- _server.addListener(_listenHost + ':' + _listenPort);
+ StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
+ int boundAddresses = 0;
+ while (tok.hasMoreTokens()) {
+ String host = tok.nextToken().trim();
+ try {
+ if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5
+ _server.addListener('[' + host + "]:" + _listenPort);
+ else
+ _server.addListener(host + ':' + _listenPort);
+ boundAddresses++;
+ } catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below
+ System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe);
+ }
+ }
+ if (boundAddresses <= 0) {
+ System.err.println("Unable to bind routerconsole to any address on port " + _listenPort);
+ return;
+ }
_server.setRootWebApp(ROUTERCONSOLE);
WebApplicationContext wac = _server.addWebApplication("/", _webAppsDir + ROUTERCONSOLE + ".war");
initialize(wac);
@@ -100,7 +126,12 @@ public class RouterConsoleRunner {
try {
_server.start();
} catch (Exception me) {
- me.printStackTrace();
+ System.err.println("WARNING: Error starting one or more listeners of the Router Console server.\n" +
+ "If your console is still accessible at http://127.0.0.1:7657/,\n" +
+ "this may be a problem only with binding to the IPV6 address ::1.\n" +
+ "If so, you may ignore this error, or remove the\n" +
+ "\"::1,\" in the \"clientApp.0.args\" line of the clients.config file.\n" +
+ "Exception: " + me);
}
try {
SysTray tray = SysTray.getInstance();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index 2e56e858bc..1b302ff0b9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -1,11 +1,15 @@
package net.i2p.router.web;
+import java.text.Collator;
import java.text.DateFormat;
import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
-import java.util.Set;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
@@ -102,6 +106,9 @@ public class SummaryHelper extends HelperBase {
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
public String getReachability() {
+ if (_context.router().getUptime() > 60*1000 && (!_context.router().gracefulShutdownInProgress()) &&
+ !_context.clientManager().isAlive())
+ return "ERR-Client Manager I2CP Error - check logs"; // not a router problem but the user should know
if (!_context.clock().getUpdatedSuccessfully())
return "ERR-ClockSkew";
if (_context.router().isHidden())
@@ -346,20 +353,16 @@ public class SummaryHelper extends HelperBase {
* @return html section summary
*/
public String getDestinations() {
- Set clients = _context.clientManager().listClients();
+ // covert the set to a list so we can sort by name and not lose duplicates
+ List clients = new ArrayList(_context.clientManager().listClients());
+ Collections.sort(clients, new AlphaComparator());
StringBuffer buf = new StringBuffer(512);
buf.append("Local destinations ");
for (Iterator iter = clients.iterator(); iter.hasNext(); ) {
Destination client = (Destination)iter.next();
- TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(client.calculateHash());
- TunnelPoolSettings out = _context.tunnelManager().getOutboundSettings(client.calculateHash());
- String name = (in != null ? in.getDestinationNickname() : null);
- if (name == null)
- name = (out != null ? out.getDestinationNickname() : null);
- if (name == null)
- name = client.calculateHash().toBase64().substring(0,6);
+ String name = getName(client);
buf.append("* ").append(name).append(" \n");
LeaseSet ls = _context.netDb().lookupLeaseSetLocally(client.calculateHash());
@@ -373,14 +376,38 @@ public class SummaryHelper extends HelperBase {
buf.append("No leases \n");
}
buf.append("Details ");
+ buf.append("\" target=\"_top\">Details ");
buf.append("Config \n");
+ buf.append("\" target=\"_top\">Config \n");
}
buf.append("\n");
return buf.toString();
}
+ private class AlphaComparator implements Comparator {
+ public int compare(Object lhs, Object rhs) {
+ String lname = getName((Destination)lhs);
+ String rname = getName((Destination)rhs);
+ if (lname.equals("shared clients"))
+ return -1;
+ if (rname.equals("shared clients"))
+ return 1;
+ return Collator.getInstance().compare(lname, rname);
+ }
+ }
+
+ private String getName(Destination d) {
+ TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(d.calculateHash());
+ String name = (in != null ? in.getDestinationNickname() : null);
+ if (name == null) {
+ TunnelPoolSettings out = _context.tunnelManager().getOutboundSettings(d.calculateHash());
+ name = (out != null ? out.getDestinationNickname() : null);
+ if (name == null)
+ name = d.calculateHash().toBase64().substring(0,6);
+ }
+ return name;
+ }
+
/**
* How many free inbound tunnels we have.
*
@@ -511,4 +538,5 @@ public class SummaryHelper extends HelperBase {
public boolean updateAvailable() {
return NewsFetcher.getInstance(_context).updateAvailable();
}
+
}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
index be39da2fd6..81f8d6e8d9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
@@ -166,6 +166,8 @@ public class UpdateHandler {
} else {
_log.log(Log.CRIT, "Update was VERIFIED, will be installed at next restart");
_status = "Update downloaded Click Restart to Install";
+ if (up.newVersion() != null)
+ _status += " Version " + up.newVersion();
}
} else {
err = err + " from " + url;
@@ -185,7 +187,7 @@ public class UpdateHandler {
}
private void restart() {
- _context.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
+ _context.addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
diff --git a/apps/routerconsole/jsp/config.jsp b/apps/routerconsole/jsp/config.jsp
index 132bd5315c..ae357d69f1 100644
--- a/apps/routerconsole/jsp/config.jsp
+++ b/apps/routerconsole/jsp/config.jsp
@@ -112,6 +112,7 @@
if you open up your port (generally 8887) to both UDP and TCP, and enable inbound TCP above.
If you think you have opened up your firewall and I2P still thinks you are firewalled, remember
that you may have multiple firewalls, for example both software packages and external hardware routers.
+ If there is an error, the logs may also help diagnose the problem.
OK - Your UDP port does not appear to be firewalled.
Firewalled - Your UDP port appears to be firewalled.
@@ -151,6 +152,9 @@
You have not configured inbound TCP with a hostname and port above, however
you have disabled UDP. Therefore your router cannot accept inbound connections.
Please configure a TCP host and port above or enable UDP.
+
ERR - Client Manager I2CP Error - check logs -
+ This is usually due to a port 7654 conflict. Check the logs to verify. Do you have another I2P instance running?
+ Stop the conflicting program and restart I2P.
-Sorry, there's no help text here yet, so check out the
+Sorry, there's not much help text here yet, so also check out the
FAQ on www.i2p2.i2p
or the
Deutsch FAQ.
+You may also try the
+forum
+or IRC.
+
Summary Bar Information
+Many of the stats on the summary bar may be
+configured to be
+graphed for further analysis.
+
+
General
+
+
Ident:
+The first four characters (24 bits) of your 44-character (256-bit) Base64 router hash.
+The full hash is shown on your router info page.
+Never reveal this to anyone, as your router info contains your IP.
+
Version:
+The version of the I2P software you are running.
+
Now:
+The current time (UTC) and the skew, if any. I2P requires your computer's time be accurate.
+If the skew is more than a few seconds, please correct the problem by adjusting
+your computer's time.
+
Reachability:
+The router's view of whether it can be contacted by other routers.
+Further information is on the configuration page.
+
+
+
Peers
+
+
Active:
+The first number is the number of peers you've sent or received a message from in the last few minutes.
+This may range from 8-10 to several hundred, depending on your total bandwidth,
+shared bandwidth, and locally-generated traffic.
+The second number is the number of peers seen in the last hour or so.
+Do not be concerned if these numbers vary widely.
+Enable graphing
+
Fast:
+This is the number of peers you use for building client tunnels. It is generally in the
+range 8-15. Your fast peers are shown on the profiles page.
+Enable graphing
+
High Capacity:
+This is the number of peers you use for building some of your exploratory tunnels. It is generally in the
+range 8-25. The fast peers are included in the high capacity tier.
+Your high capacity peers are shown on the profiles page.
+Enable graphing
+
Well Integrated:
+This is the number of peers you use for network database inquiries.
+These are usually the "floodfill" peers.
+Your well integrated peers are shown on the bottom of the profiles page.
+
Known:
+This is the total number of routers you know about.
+They are listed on the network database page.
+This may range from under 100 to 1000 or more.
+This number is not the total size of the network;
+it may vary widely depending on your total bandwidth,
+shared bandwidth, and locally-generated traffic.
+I2P does not require a router to know every other router.
+
+
+
Bandwidth in/out
+Should be self-explanatory. All values are in bytes per second, not bits per second.
+Change your bandwidth limits on the configuration page.
+Bandwidth is graphed by default.
+
+
Local destinations
+The local applications connecting through your router.
+These may be clients started through I2PTunnel
+or external programs connecting through SAM, BOB, or directly to I2CP.
+
+
Exploratory:
+Tunnels built by your router and used for communication with the floodfill peers,
+building new tunnels, and testing existing tunnels.
+
Client:
+Tunnels built by your router for each client's use.
+
Participating:
+Tunnels built by other routers through your router.
+This may vary widely depending on network demand, your
+shared bandwidth, and amount of locally-generated traffic.
+The recommended method for limiting participating tunnels is
+to change your share percentage on the configuration page.
+You may also limit the total number by setting router.maxParticipatingTunnels=nnn on
+the advanced configuration page.
+Enable graphing
+
+
+
Congestion
+Some basic indications of router overload.
+
+
Job lag:
+How long jobs are waiting before execution. The job queue is listed on the jobs page.
+Unfortunately, there are several other job queues in the router that may be congested,
+and their status is not available in the router console.
+The job lag should generally be zero.
+If it is consistently higher than 500ms, your computer is very slow, or the
+router has serious problems.
+Enable graphing
+
Message delay:
+How long an outbound message waits in the queue.
+This should generally be a few hundred milliseconds or less.
+If it is consistently higher than 1000ms, your computer is very slow,
+or you should adjust your bandwidth limits, or your (bittorrent?) clients
+may be sending too much data and should have their transmit bandwidth limit reduced.
+Enable graphing (transport.sendProcessingTime)
+
Tunnel lag:
+This is the round trip time for a tunnel test, which sends a single message
+out a client tunnel and in an exploratory tunnel, or vice versa.
+It should usually be less than 5 seconds.
+If it is consistently higher than that, your computer is very slow,
+or you should adjust your bandwidth limits, or there are network problems.
+Enable graphing (tunnel.testSuccessTime)
+
Handle backlog:
+This is the number of pending requests from other routers to build a
+participating tunnel through your router.
+It should usually be close to zero.
+If it is consistently high, your computer is too slow,
+and you should reduce your share bandwidth limits.
+
Accepting/Rejecting:
+Your routers' status on accepting or rejecting
+requests from other routers to build a
+participating tunnel through your router.
+Your router may accept all requests, accept or reject a percentage of requests,
+or reject all requests for a number of reasons, to control
+the bandwidth and CPU demands and maintain capacity for
+local clients.
+
+
Legal stuff
The I2P router (router.jar) and SDK (i2p.jar) are almost entirely public domain, with
a few notable exceptions:
diff --git a/apps/routerconsole/jsp/nav.jsp b/apps/routerconsole/jsp/nav.jsp
index 22bb8ec245..914371c78d 100644
--- a/apps/routerconsole/jsp/nav.jsp
+++ b/apps/routerconsole/jsp/nav.jsp
@@ -25,7 +25,7 @@
SusiDNS |
I2PSnark |
- My Eepsite
+ My Eepsite I2PTunnel |
Tunnels |
Profiles |
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
index 6b99fdc00a..f73632683c 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
@@ -187,12 +187,12 @@ public class Connection {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _options.getWindowSize() + "/"
+ _activeResends + "), waiting " + timeLeft);
- try { _outboundPackets.wait(Math.min(timeLeft,250l)); } catch (InterruptedException ie) {}
+ try { _outboundPackets.wait(Math.min(timeLeft,250l)); } catch (InterruptedException ie) { if (_log.shouldLog(Log.DEBUG)) _log.debug("InterruptedException while Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends +")"); return false;}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends
+ "), waiting indefinitely");
- try { _outboundPackets.wait(250); } catch (InterruptedException ie) {} //10*1000
+ try { _outboundPackets.wait(250); } catch (InterruptedException ie) {if (_log.shouldLog(Log.DEBUG)) _log.debug("InterruptedException while Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends + ")"); return false;} //10*1000
}
} else {
_context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start);
@@ -810,7 +810,11 @@ public class Connection {
synchronized (_connectLock) {
_connectLock.wait(timeLeft);
}
- } catch (InterruptedException ie) {}
+ } catch (InterruptedException ie) {
+ if (_log.shouldLog(Log.DEBUG)) _log.debug("waitForConnect(): InterruptedException");
+ _connectionError = "InterruptedException";
+ return;
+ }
}
}
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
index b96eaea633..2d198ad66e 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
@@ -126,7 +126,7 @@ class ConnectionHandler {
if (timeoutMs <= 0) {
try {
syn = _synQueue.take(); // waits forever
- } catch (InterruptedException ie) {}
+ } catch (InterruptedException ie) { break;}
} else {
long remaining = expiration - _context.clock().now();
// (dont think this applies anymore for LinkedBlockingQueue)
@@ -138,7 +138,7 @@ class ConnectionHandler {
break;
try {
syn = _synQueue.poll(remaining, TimeUnit.MILLISECONDS); // waits the specified time max
- } catch (InterruptedException ie) {}
+ } catch (InterruptedException ie) { }
break;
}
}
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java
index cbb89e79ec..197b927541 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java
@@ -213,7 +213,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
timeRemaining = 10*1000;
wait(timeRemaining);
}
- } catch (InterruptedException ie) {}
+ } catch (InterruptedException ie) { break; }
}
if (!writeSuccessful())
releasePayload();
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java
index a56e7753dd..e91cbdb7d4 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java
@@ -89,9 +89,17 @@ class PacketQueue {
// so if we retransmit it will use a new tunnel/lease combo
expires = rpe.getNextSendTime() - 500;
if (expires > 0)
- sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires);
+ // I2PSessionImpl2
+ //sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires);
+ // I2PSessionMuxedImpl
+ sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires,
+ I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
else
- sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent);
+ // I2PSessionImpl2
+ //sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, 0);
+ // I2PSessionMuxedImpl
+ sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent,
+ I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
end = _context.clock().now();
if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) )
diff --git a/apps/susidns/src/jsp/addressbook.jsp b/apps/susidns/src/jsp/addressbook.jsp
index 05a012936b..d36b913541 100644
--- a/apps/susidns/src/jsp/addressbook.jsp
+++ b/apps/susidns/src/jsp/addressbook.jsp
@@ -160,7 +160,8 @@
Add new destination:
-Hostname: Destination:
+Hostname:
+Destination:
diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
index a80fdc5914..6e9fad1c5e 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
@@ -76,7 +76,7 @@ public class WebMail extends HttpServlet
private static final int BUFSIZE = 4096;
- private static final String DEFAULT_HOST = "localhost";
+ private static final String DEFAULT_HOST = "127.0.0.1";
private static final int DEFAULT_POP3PORT = 7660;
private static final int DEFAULT_SMTPPORT = 7659;
diff --git a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
index 4a635fd080..652ff66771 100644
--- a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
+++ b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
@@ -132,7 +132,7 @@ public class SysTray implements SysTrayMenuListener {
public void iconLeftClicked(SysTrayMenuEvent e) {}
public void iconLeftDoubleClicked(SysTrayMenuEvent e) {
- openRouterConsole("http://localhost:" + _portString + "/index.jsp");
+ openRouterConsole("http://127.0.0.1:" + _portString + "/index.jsp");
}
public void menuItemSelected(SysTrayMenuEvent e) {
@@ -153,7 +153,7 @@ public class SysTray implements SysTrayMenuListener {
if (!(browser = promptForBrowser("Select browser")).equals("nullnull"))
setBrowser(browser);
} else if (e.getActionCommand().equals("openconsole")) {
- openRouterConsole("http://localhost:" + _portString + "/index.jsp");
+ openRouterConsole("http://127.0.0.1:" + _portString + "/index.jsp");
}
}
diff --git a/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java b/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java
index 5487f5faf5..c7524054e7 100644
--- a/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java
+++ b/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java
@@ -163,7 +163,7 @@ public class UrlLauncher {
if (args.length > 0)
launcher.openUrl(args[0]);
else
- launcher.openUrl("http://localhost:7657/index.jsp");
+ launcher.openUrl("http://127.0.0.1:7657/index.jsp");
} catch (Exception e) {}
}
}
diff --git a/build.xml b/build.xml
index a9f58ef202..3a30ff8e7f 100644
--- a/build.xml
+++ b/build.xml
@@ -276,6 +276,7 @@
+
-
+
@@ -486,4 +487,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/checklist.txt b/checklist.txt
index b649bef590..fcbe650284 100644
--- a/checklist.txt
+++ b/checklist.txt
@@ -15,45 +15,45 @@ Change revision in:
core/java/src/net/i2p/CoreVersion.java
Review the complete diff from the last release:
- mtn diff -r t:i2p-0.6.(xx-1) > out.diff
+ mtn diff -r t:i2p-0.7.(xx-1) > out.diff
vi out.diff
Build and tag:
ant pkg
mtn ci
- mtn tag h: i2p-0.6.xx
+ mtn tag h: i2p-0.7.xx
Sync with mtn.i2p2.i2p
Create a signed update file with:
export I2P=~/i2p
- java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign i2pupdate.zip i2pupdate.sud /path/to/private.key 0.6.xx
+ java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign i2pupdate.zip i2pupdate.sud /path/to/private.key 0.7.xx
Verify signed update file with:
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion i2pupdate.sud
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate verifysig i2pupdate.sud
Make the source tarball:
- Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p i2p-0.6.xx
+ Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p i2p-0.7.xx
Double-check trust list
- tar cjf i2psource-0.6.xx.tar.bz2 --exclude i2p-0.6.xx/_MTN i2p-0.6.xx
- mv i2p-0.6.xx.tar.bz2 i2p.i2p
+ tar cjf i2psource-0.7.xx.tar.bz2 --exclude i2p-0.7.xx/_MTN i2p-0.7.xx
+ mv i2p-0.7.xx.tar.bz2 i2p.i2p
Until the build script gets this ability, you need to rename some files:
- mv i2pinstall.exe i2pinstall-0.6.xx.exe
- mv i2p.tar.bz2 i2pheadless-0.6.xx.tar.bz2
- mv i2pupdate.zip i2pupdate-0.6.xx.zip
+ mv i2pinstall.exe i2pinstall-0.7.xx.exe
+ mv i2p.tar.bz2 i2pheadless-0.7.xx.tar.bz2
+ mv i2pupdate.zip i2pupdate-0.7.xx.zip
you probably don't need to rename i2pupdate.sud
Generate hashes:
- sha1sum i2p*0.6.xx.*
+ sha1sum i2p*0.7.xx.*
sha1sum i2pupdate.sud
now GPG-sign an announcement with the hashes
Generate PGP signatures:
- gpg -b i2pinstall-0.6.xx.exe
- gpg -b i2pheadless-0.6.xx.tar.bz2
- gpg -b i2psource-0.6.xx.tar.bz2
- gpg -b i2pupdate-0.6.xx.zip
+ gpg -b i2pinstall-0.7.xx.exe
+ gpg -b i2pheadless-0.7.xx.tar.bz2
+ gpg -b i2psource-0.7.xx.tar.bz2
+ gpg -b i2pupdate-0.7.xx.zip
gpg -b i2pupdate.sud
Distribute files to download locations and to www.i2p2.i2p
diff --git a/core/c/jbigi/build-all.sh b/core/c/jbigi/build-all.sh
index 65456ee50b..861a1e0a4e 100755
--- a/core/c/jbigi/build-all.sh
+++ b/core/c/jbigi/build-all.sh
@@ -12,7 +12,7 @@ FreeBSD*)
exit;;
esac
-VER=4.2.2
+VER=4.2.4
echo "Extracting GMP Version $VER ..."
tar -xjf gmp-$VER.tar.bz2
echo "Building..."
@@ -21,7 +21,8 @@ mkdir lib
mkdir lib/net
mkdir lib/net/i2p
mkdir lib/net/i2p/util
-for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon
+
+for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon geode pentiumm core2
do
mkdir bin/$x
cd bin/$x
diff --git a/core/c/jbigi/build.sh b/core/c/jbigi/build.sh
index 39969b13c9..7ef9000704 100755
--- a/core/c/jbigi/build.sh
+++ b/core/c/jbigi/build.sh
@@ -15,7 +15,7 @@
mkdir -p lib/
mkdir -p bin/local
-VER=4.2.2
+VER=4.2.4
if [ "$1" != "dynamic" -a ! -d gmp-$VER ]
then
diff --git a/core/c/jbigi/build_jbigi.sh b/core/c/jbigi/build_jbigi.sh
index 859eda329a..8ff3219b8a 100755
--- a/core/c/jbigi/build_jbigi.sh
+++ b/core/c/jbigi/build_jbigi.sh
@@ -1,6 +1,7 @@
#!/bin/sh
# When executed in Mingw: Produces an jbigi.dll
-# When executed in Linux: Produces an libjbigi.so
+# When executed in Linux/FreeBSD: Produces an libjbigi.so
+# Darwin produces libjbigi.jnilib, right?
CC="gcc"
@@ -32,7 +33,8 @@ esac
#To link dynamically to GMP (use libgmp.so or gmp.lib), uncomment the first line below
#To link statically to GMP, uncomment the second line below
-if test $1 = "dynamic"
+# Bug!!! Quote *BOTH* or neither! --Sponge
+if test "$1" = "dynamic"
then
echo "Building jbigi lib that is dynamically linked to GMP"
LIBPATH="-L.libs"
diff --git a/core/c/jbigi/mbuild-all.sh b/core/c/jbigi/mbuild-all.sh
new file mode 100755
index 0000000000..a97139dfca
--- /dev/null
+++ b/core/c/jbigi/mbuild-all.sh
@@ -0,0 +1,140 @@
+#/bin/bash
+
+# TO-DO: Darwin.
+
+# Note: You will have to add the CPU ID for the platform in the CPU ID code
+# for a new CPU. Just adding them here won't let I2P use the code!
+
+#
+# If you know of other platforms i2p on linux works on,
+# please add them here.
+# Do NOT add any X86 platforms, do that below in the x86 platform list.
+#
+MISC_LINUX_PLATFORMS="hppa2.0 alphaev56 armv5tel mips64el itanium itanium2 ultrasparc2 ultrasparc2i alphaev6 powerpc970 powerpc7455 powerpc7447 atom"
+
+#
+# If you know of other platforms i2p on FREEBSD works on,
+# please add them here.
+# Do NOT add any X86 platforms, do that below in the x86 platform list.
+#
+MISC_FREEBSD_PLATFORMS="atom alphaev56 ultrasparc2i"
+
+#
+# MINGW/Windows??
+#
+MISC_MINGW_PLATFORMS=""
+
+#
+# Are there any other X86 platforms that work on i2p? Add them here.
+#
+# Oddly athlon64 builds.... I wonder what others can :-)
+#
+X86_PLATFORMS="pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon pentiumm core2 athlon64 geode"
+
+
+#
+# You should not need to edit anything below this comment.
+#
+
+MINGW_PLATFORMS="${X86_PLATFORMS} ${MISC_MINGW_PLATFORMS}"
+LINUX_PLATFORMS="${X86_PLATFORMS} ${MISC_LINUX_PLATFORMS}"
+FREEBSD_PLATFORMS="${X86_PLATFORMS} ${MISC_FREEBSD_PLATFORMS}"
+
+VER=$(echo gmp-*.tar.bz2 | sed -re "s/(.*-)(.*)(.*.tar.bz2)$/\2/" | tail --lines=1)
+if [ "$VER" == "" ] ; then
+ echo "ERROR! Can't find gmp source tarball."
+ exit 1
+fi
+
+
+case `uname -sr` in
+MINGW*)
+ PLATFORM_LIST="${MINGW_PLATFORMS}"
+ NAME="jbigi"
+ TYPE="dll"
+ TARGET="-windows-"
+ echo "Building windows .dlls for all architectures";;
+Linux*)
+ PLATFORM_LIST="${LINUX_PLATFORMS}"
+ NAME="libjbigi"
+ TYPE="so"
+ TARGET="-linux-"
+ echo "Building linux .sos for all architectures";;
+FreeBSD*)
+ PLATFORM_LIST="${FREEBSD_PLATFORMS}"
+ NAME="libjbigi"
+ TYPE="so"
+ TARGET="-freebsd-"
+ echo "Building freebsd .sos for all architectures";;
+*)
+ echo "Unsupported build environment"
+ exit;;
+esac
+
+function make_static {
+ echo "Attempting .${4} creation for ${3}${5}${2}"
+ ../../mbuild_jbigi.sh static || return 1
+ cp ${3}.${4} ../../lib/net/i2p/util/${3}${5}${2}.${4}
+ return 0
+}
+
+function make_file {
+ # Nonfatal bail out on Failed build.
+ echo "Attempting build for ${3}${5}${2}"
+ make && return 0
+ cd ..
+ rm -R "$2"
+ echo -e "\n\nFAILED! ${3}${5}${2} not made.\a"
+ sleep 10
+ return 1
+}
+
+function configure_file {
+ echo -e "\n\n\nAttempting configure for ${3}${5}${2}\n\n\n"
+ sleep 10
+ # Nonfatal bail out on unsupported platform.
+ ../../gmp-${1}/configure --build=${2} && return 0
+ cd ..
+ rm -R "$2"
+ echo -e "\n\nSorry, ${3}${5}${2} is not supported on your build environment.\a"
+ sleep 10
+ return 1
+}
+
+function build_file {
+ configure_file "$1" "$2" "$3" "$4" "$5" && make_file "$1" "$2" "$3" "$4" "$5" && make_static "$1" "$2" "$3" "$4" "$5" && return 0
+ echo -e "\n\n\nError building static!\n\n\a"
+ sleep 10
+ return 1
+}
+
+echo "Extracting GMP Version $VER ..."
+tar -xf gmp-$VER.tar.bz2 || ( echo "Error in tarball file!" ; exit 1 )
+
+if [ ! -d bin ]; then
+ mkdir bin
+fi
+if [ ! -d lib/net/i2p/util ]; then
+ mkdir -p lib/net/i2p/util
+fi
+
+# Don't touch this one.
+NO_PLATFORM=none
+
+for x in $NO_PLATFORM $PLATFORM_LIST
+do
+ (
+ if [ ! -d bin/$x ]; then
+ mkdir bin/$x
+ cd bin/$x
+ else
+ cd bin/$x
+ rm -Rf *
+ fi
+
+ build_file "$VER" "$x" "$NAME" "$TYPE" "$TARGET"
+ )
+done
+
+echo "Success!"
+exit 0
diff --git a/core/c/jbigi/mbuild_jbigi.sh b/core/c/jbigi/mbuild_jbigi.sh
new file mode 100755
index 0000000000..1e262a6031
--- /dev/null
+++ b/core/c/jbigi/mbuild_jbigi.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# When executed in Mingw: Produces an jbigi.dll
+# When executed in Linux/FreeBSD: Produces an libjbigi.so
+# What does Darwin produce? libjbigi.jnilib?
+CC="gcc"
+
+case `uname -sr` in
+MINGW*)
+ JAVA_HOME="c:/software/j2sdk1.4.2_05"
+ COMPILEFLAGS="-Wall"
+ INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include/win32/ -I$JAVA_HOME/include/"
+ LINKFLAGS="-shared -Wl,--kill-at"
+ LIBFILE="jbigi.dll";;
+CYGWIN*)
+ JAVA_HOME="c:/software/j2sdk1.4.2_05"
+ COMPILEFLAGS="-Wall -mno-cygwin"
+ INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include/win32/ -I$JAVA_HOME/include/"
+ LINKFLAGS="-shared -Wl,--kill-at"
+ LIBFILE="jbigi.dll";;
+Darwin*)
+ JAVA_HOME="/Library/Java/Home"
+ COMPILEFLAGS="-Wall"
+ INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include"
+ LINKFLAGS="-dynamiclib -framework JavaVM"
+ LIBFILE="libjbigi.jnilib";;
+*)
+ COMPILEFLAGS="-fPIC -Wall"
+ INCLUDES="-I. -I../../jbigi/include -I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
+ LINKFLAGS="-shared -Wl,-soname,libjbigi.so"
+ LIBFILE="libjbigi.so";;
+esac
+
+if [ "$1" = "dynamic" ] ; then
+ echo "Building a jbigi lib that is dynamically linked to GMP"
+ LIBPATH="-L.libs"
+ INCLUDELIBS="-lgmp"
+else
+ echo "Building a jbigi lib that is statically linked to GMP"
+ STATICLIBS=".libs/libgmp.a"
+fi
+
+echo "Compiling C code..."
+rm -f jbigi.o $LIBFILE
+$CC -c $COMPILEFLAGS $INCLUDES ../../jbigi/src/jbigi.c || exit 1
+$CC $LINKFLAGS $INCLUDES $INCLUDELIBS -o $LIBFILE jbigi.o $STATICLIBS || exit 1
+
+exit 0
diff --git a/core/c/mbuild.sh b/core/c/mbuild.sh
new file mode 100755
index 0000000000..4591d5b7c8
--- /dev/null
+++ b/core/c/mbuild.sh
@@ -0,0 +1,28 @@
+#/bin/bash
+
+JBIGI=../../../installer/lib/jbigi/jbigi.jar
+
+if [ -f jbigi.jarx ] ; then
+JBIGI=../jbigi.jar
+fi
+
+(cd jcpuid ; sh build.sh ; cd ..)
+(cd jbigi ; sh mbuild-all.sh ; cd ..)
+
+mkdir t
+
+(
+ cd t
+ jar xf ../../../installer/lib/jbigi/jbigi.jar
+)
+
+cp jbigi/lib/net/i2p/util/* t/
+cp jcpuid/lib/freenet/support/CPUInformation/* t/
+
+(
+ cd t
+ jar cf ../jbigi.jar .
+)
+
+rm -R t
+echo "jbigi.jar Refreshed."
diff --git a/core/java/nbproject/project.xml b/core/java/nbproject/project.xml
new file mode 100644
index 0000000000..f992a41dac
--- /dev/null
+++ b/core/java/nbproject/project.xml
@@ -0,0 +1,91 @@
+
+
+ org.netbeans.modules.ant.freeform
+
+
+ i2p_sdk
+
+
+
+ i2p_sdk
+
+
+
+
+ java
+ src
+ UTF-8
+
+
+
+ java
+ test
+ UTF-8
+
+
+
+ .
+ UTF-8
+
+
+
+
+ build
+
+
+ clean
+
+
+ javadoc
+
+
+ test
+
+
+ clean
+ build
+
+
+
+ folder
+ build/obj
+ build
+
+
+
+
+
+ src
+
+
+
+ test
+
+
+ build.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ src
+ build/obj
+ build/javadoc
+ 1.5
+
+
+ test
+
+ 1.5
+
+
+
+
diff --git a/core/java/src/net/i2p/CoreVersion.java b/core/java/src/net/i2p/CoreVersion.java
index c24542b009..6c924fe109 100644
--- a/core/java/src/net/i2p/CoreVersion.java
+++ b/core/java/src/net/i2p/CoreVersion.java
@@ -15,7 +15,7 @@ package net.i2p;
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.72 $ $Date: 2008-08-24 12:00:00 $";
- public final static String VERSION = "0.7";
+ public final static String VERSION = "0.7.1";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index 6b3b0fd5bf..f26f74ab7b 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -23,6 +23,7 @@ import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
+import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FortunaRandomSource;
import net.i2p.util.KeyRing;
import net.i2p.util.LogManager;
@@ -94,6 +95,7 @@ public class I2PAppContext {
private volatile boolean _randomInitialized;
private volatile boolean _keyGeneratorInitialized;
protected volatile boolean _keyRingInitialized; // used in RouterContext
+ private Set _shutdownTasks;
/**
@@ -152,6 +154,7 @@ public class I2PAppContext {
_elGamalAESEngineInitialized = false;
_logManagerInitialized = false;
_keyRingInitialized = false;
+ _shutdownTasks = new ConcurrentHashSet(0);
}
/**
@@ -557,4 +560,13 @@ public class I2PAppContext {
_randomInitialized = true;
}
}
+
+ public void addShutdownTask(Runnable task) {
+ _shutdownTasks.add(task);
+ }
+
+ public Set getShutdownTasks() {
+ return new HashSet(_shutdownTasks);
+ }
+
}
diff --git a/core/java/src/net/i2p/client/BWLimitsMessageHandler.java b/core/java/src/net/i2p/client/BWLimitsMessageHandler.java
new file mode 100644
index 0000000000..b47eaa6c8f
--- /dev/null
+++ b/core/java/src/net/i2p/client/BWLimitsMessageHandler.java
@@ -0,0 +1,25 @@
+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.BandwidthLimitsMessage;
+
+/**
+ * Handle I2CP BW replies from the router
+ */
+class BWLimitsMessageHandler extends HandlerImpl {
+ public BWLimitsMessageHandler(I2PAppContext ctx) {
+ super(ctx, BandwidthLimitsMessage.MESSAGE_TYPE);
+ }
+
+ public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
+ _log.debug("Handle message " + message);
+ BandwidthLimitsMessage msg = (BandwidthLimitsMessage) message;
+ ((I2PSimpleSession)session).bwReceived(msg.getLimits());
+ }
+}
diff --git a/core/java/src/net/i2p/client/I2PClientImpl.java b/core/java/src/net/i2p/client/I2PClientImpl.java
index 4783458a3a..5b1b44867d 100644
--- a/core/java/src/net/i2p/client/I2PClientImpl.java
+++ b/core/java/src/net/i2p/client/I2PClientImpl.java
@@ -77,6 +77,6 @@ class I2PClientImpl implements I2PClient {
*
*/
public I2PSession createSession(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
- return new I2PSessionImpl2(context, destKeyStream, options); // thread safe
+ return new I2PSessionMuxedImpl(context, destKeyStream, options); // thread safe and muxed
}
}
diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java
index d8c64f2222..1998dad55a 100644
--- a/core/java/src/net/i2p/client/I2PSession.java
+++ b/core/java/src/net/i2p/client/I2PSession.java
@@ -40,6 +40,8 @@ public interface I2PSession {
*/
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException;
+ /** See I2PSessionMuxedImpl for details */
+ public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException;
/**
* Like sendMessage above, except the key used and the tags sent are exposed to the
@@ -71,6 +73,12 @@ public interface I2PSession {
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException;
+ /** See I2PSessionMuxedImpl for details */
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
+ int proto, int fromport, int toport) throws I2PSessionException;
+ /** See I2PSessionMuxedImpl for details */
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
+ int proto, int fromport, int toport) throws I2PSessionException;
/** Receive a message that the router has notified the client about, returning
* the payload.
@@ -134,4 +142,23 @@ public interface I2PSession {
*
*/
public Destination lookupDest(Hash h) throws I2PSessionException;
+
+ /**
+ * Get the current bandwidth limits
+ */
+ public int[] bandwidthLimits() throws I2PSessionException;
+
+ /** See I2PSessionMuxedImpl for details */
+ public void addSessionListener(I2PSessionListener lsnr, int proto, int port);
+ /** See I2PSessionMuxedImpl for details */
+ public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port);
+ /** See I2PSessionMuxedImpl for details */
+ public void removeListener(int proto, int port);
+
+ public static final int PORT_ANY = 0;
+ public static final int PORT_UNSPECIFIED = 0;
+ public static final int PROTO_ANY = 0;
+ public static final int PROTO_UNSPECIFIED = 0;
+ public static final int PROTO_STREAMING = 6;
+ public static final int PROTO_DATAGRAM = 17;
}
diff --git a/core/java/src/net/i2p/client/I2PSessionDemultiplexer.java b/core/java/src/net/i2p/client/I2PSessionDemultiplexer.java
new file mode 100644
index 0000000000..9a1ff42e31
--- /dev/null
+++ b/core/java/src/net/i2p/client/I2PSessionDemultiplexer.java
@@ -0,0 +1,135 @@
+package net.i2p.client;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+import net.i2p.I2PAppContext;
+import net.i2p.util.Log;
+
+/*
+ * public domain
+ */
+
+/**
+ * Implement multiplexing with a 1-byte 'protocol' and a two-byte 'port'.
+ * Listeners register with either addListener() or addMuxedListener(),
+ * depending on whether they want to hear about the
+ * protocol, from port, and to port for every received message.
+ *
+ * This only calls one listener, not all that apply.
+ *
+ * @author zzz
+ */
+public class I2PSessionDemultiplexer implements I2PSessionMuxedListener {
+ private Log _log;
+ private Map _listeners;
+
+ public I2PSessionDemultiplexer(I2PAppContext ctx) {
+ _log = ctx.logManager().getLog(I2PSessionDemultiplexer.class);
+ _listeners = new ConcurrentHashMap();
+ }
+
+ /** unused */
+ public void messageAvailable(I2PSession session, int msgId, long size) {}
+
+ public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport ) {
+ I2PSessionMuxedListener l = findListener(proto, toport);
+ if (l != null)
+ l.messageAvailable(session, msgId, size, proto, fromport, toport);
+ else {
+ // no listener, throw it out
+ _log.error("No listener found for proto: " + proto + " port: " + toport + "msg id: " + msgId +
+ " from pool of " + _listeners.size() + " listeners");
+ try {
+ session.receiveMessage(msgId);
+ } catch (I2PSessionException ise) {}
+ }
+ }
+
+ public void reportAbuse(I2PSession session, int severity) {
+ for (I2PSessionMuxedListener l : _listeners.values())
+ l.reportAbuse(session, severity);
+ }
+
+ public void disconnected(I2PSession session) {
+ for (I2PSessionMuxedListener l : _listeners.values())
+ l.disconnected(session);
+ }
+
+ public void errorOccurred(I2PSession session, String message, Throwable error) {
+ for (I2PSessionMuxedListener l : _listeners.values())
+ l.errorOccurred(session, message, error);
+ }
+
+ /**
+ * For those that don't need to hear about the protocol and ports
+ * in messageAvailable()
+ * (Streaming lib)
+ */
+ public void addListener(I2PSessionListener l, int proto, int port) {
+ _listeners.put(key(proto, port), new NoPortsListener(l));
+ }
+
+ /**
+ * For those that do care
+ * UDP perhaps
+ */
+ public void addMuxedListener(I2PSessionMuxedListener l, int proto, int port) {
+ _listeners.put(key(proto, port), l);
+ }
+
+ public void removeListener(int proto, int port) {
+ _listeners.remove(key(proto, port));
+ }
+
+ /** find the one listener that most specifically matches the request */
+ private I2PSessionMuxedListener findListener(int proto, int port) {
+ I2PSessionMuxedListener rv = getListener(proto, port);
+ if (rv != null) return rv;
+ if (port != I2PSession.PORT_ANY) { // try any port
+ rv = getListener(proto, I2PSession.PORT_ANY);
+ if (rv != null) return rv;
+ }
+ if (proto != I2PSession.PROTO_ANY) { // try any protocol
+ rv = getListener(I2PSession.PROTO_ANY, port);
+ if (rv != null) return rv;
+ }
+ if (proto != I2PSession.PROTO_ANY && port != I2PSession.PORT_ANY) { // try default
+ rv = getListener(I2PSession.PROTO_ANY, I2PSession.PORT_ANY);
+ }
+ return rv;
+ }
+
+ private I2PSessionMuxedListener getListener(int proto, int port) {
+ return _listeners.get(key(proto, port));
+ }
+
+ private Integer key(int proto, int port) {
+ return Integer.valueOf(((port << 8) & 0xffff00) | proto);
+ }
+
+ /** for those that don't care about proto and ports */
+ private static class NoPortsListener implements I2PSessionMuxedListener {
+ private I2PSessionListener _l;
+
+ public NoPortsListener(I2PSessionListener l) {
+ _l = l;
+ }
+
+ public void messageAvailable(I2PSession session, int msgId, long size) {
+ throw new IllegalArgumentException("no");
+ }
+ public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
+ _l.messageAvailable(session, msgId, size);
+ }
+ public void reportAbuse(I2PSession session, int severity) {
+ _l.reportAbuse(session, severity);
+ }
+ public void disconnected(I2PSession session) {
+ _l.disconnected(session);
+ }
+ public void errorOccurred(I2PSession session, String message, Throwable error) {
+ _l.errorOccurred(session, message, error);
+ }
+ }
+}
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index 00da88aa22..9e42eef5fa 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -77,12 +77,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected OutputStream _out;
/** who we send events to */
- private I2PSessionListener _sessionListener;
+ protected I2PSessionListener _sessionListener;
/** class that generates new messages */
protected I2CPMessageProducer _producer;
/** map of Long --> MessagePayloadMessage */
- private Map _availableMessages;
+ protected Map _availableMessages;
protected I2PClientMessageHandlerMap _handlerMap;
@@ -162,7 +162,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected void loadConfig(Properties options) {
_options = new Properties();
_options.putAll(filter(options));
- _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
+ _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + "");
try {
_portNum = Integer.parseInt(portNum);
@@ -366,14 +366,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
SimpleScheduler.getInstance().addEvent(new VerifyUsage(mid), 30*1000);
}
- private class VerifyUsage implements SimpleTimer.TimedEvent {
+ protected class VerifyUsage implements SimpleTimer.TimedEvent {
private Long _msgId;
public VerifyUsage(Long id) { _msgId = id; }
public void timeReached() {
MessagePayloadMessage removed = _availableMessages.remove(_msgId);
if (removed != null && !isClosed())
- _log.log(Log.CRIT, "Message NOT removed! id=" + _msgId + ": " + removed);
+ _log.error("Message NOT removed! id=" + _msgId + ": " + removed);
}
}
@@ -656,6 +656,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
return null;
}
+ public int[] bandwidthLimits() throws I2PSessionException {
+ return null;
+ }
+
protected void updateActivity() {
_lastActivity = _context.clock().now();
if (_isReduced) {
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl2.java b/core/java/src/net/i2p/client/I2PSessionImpl2.java
index 56ef88974b..9abce4b72b 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl2.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl2.java
@@ -93,7 +93,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* set to false.
*/
private static final int DONT_COMPRESS_SIZE = 66;
- private boolean shouldCompress(int size) {
+ protected boolean shouldCompress(int size) {
if (size <= DONT_COMPRESS_SIZE)
return false;
String p = getOptions().getProperty("i2cp.gzip");
@@ -102,12 +102,35 @@ class I2PSessionImpl2 extends I2PSessionImpl {
return SHOULD_COMPRESS;
}
+ public void addSessionListener(I2PSessionListener lsnr, int proto, int port) {
+ throw new IllegalArgumentException("Use MuxedImpl");
+ }
+ public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) {
+ throw new IllegalArgumentException("Use MuxedImpl");
+ }
+ public void removeListener(int proto, int port) {
+ throw new IllegalArgumentException("Use MuxedImpl");
+ }
+ public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException {
+ throw new IllegalArgumentException("Use MuxedImpl");
+ }
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
+ int proto, int fromport, int toport) throws I2PSessionException {
+ throw new IllegalArgumentException("Use MuxedImpl");
+ }
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
+ int proto, int fromport, int toport) throws I2PSessionException {
+ throw new IllegalArgumentException("Use MuxedImpl");
+ }
+
@Override
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length);
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException {
- return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64), 0);
+ // we don't do end-to-end crypto any more
+ //return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64), 0);
+ return sendMessage(dest, payload, offset, size, null, null, 0);
}
@Override
@@ -173,7 +196,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private static final int NUM_TAGS = 50;
- private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
+ protected boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException {
SessionKey key = null;
SessionKey newKey = null;
diff --git a/core/java/src/net/i2p/client/I2PSessionListener.java b/core/java/src/net/i2p/client/I2PSessionListener.java
index 4c78c65272..740ebeeab3 100644
--- a/core/java/src/net/i2p/client/I2PSessionListener.java
+++ b/core/java/src/net/i2p/client/I2PSessionListener.java
@@ -20,7 +20,7 @@ public interface I2PSessionListener {
* size # of bytes.
* @param session session to notify
* @param msgId message number available
- * @param size size of the message
+ * @param size size of the message - why it's a long and not an int is a mystery
*/
void messageAvailable(I2PSession session, int msgId, long size);
@@ -42,4 +42,4 @@ public interface I2PSessionListener {
*
*/
void errorOccurred(I2PSession session, String message, Throwable error);
-}
\ No newline at end of file
+}
diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
new file mode 100644
index 0000000000..a3e5e57a7f
--- /dev/null
+++ b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
@@ -0,0 +1,320 @@
+package net.i2p.client;
+
+/*
+ * public domain
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
+import net.i2p.data.SessionKey;
+import net.i2p.data.SessionTag;
+import net.i2p.data.i2cp.MessagePayloadMessage;
+import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
+
+/**
+ * I2PSession with protocol and ports
+ *
+ * Streaming lib has been modified to send I2PSession.PROTO_STREAMING but
+ * still receives all. It sends with fromPort and toPort = 0, and receives on all ports.
+ *
+ * No datagram apps have been modified yet.
+
+ * Therefore the compatibility situation is as follows:
+ *
+ * Compatibility:
+ * old streaming -> new streaming: sends proto anything, rcvs proto anything
+ * new streaming -> old streaming: sends PROTO_STREAMING, ignores rcvd proto
+ * old datagram -> new datagram: sends proto anything, rcvs proto anything
+ * new datagram -> old datagram: sends PROTO_DATAGRAM, ignores rcvd proto
+ * In all the above cases, streaming and datagram receive traffic for the other
+ * protocol, same as before.
+ *
+ * old datagram -> new muxed: doesn't work because the old sends proto 0 but the udp side
+ * of the mux registers with PROTO_DATAGRAM, so the datagrams
+ * go to the streaming side, same as before.
+ * old streaming -> new muxed: works
+ *
+ * Typical Usage:
+ * Streaming + datagrams:
+ * I2PSocketManager sockMgr = getSocketManager();
+ * I2PSession session = sockMgr.getSession();
+ * session.addMuxedSessionListener(myI2PSessionMuxedListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
+ * * or *
+ * session.addSessionListener(myI2PSessionListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
+ * session.sendMessage(dest, payload, I2PSession.PROTO_DATAGRAM, fromPort, toPort);
+ *
+ * Datagrams only, with multiple ports:
+ * I2PClient client = I2PClientFactory.createClient();
+ * ...
+ * I2PSession session = client.createSession(...);
+ * session.addMuxedSessionListener(myI2PSessionMuxedListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
+ * * or *
+ * session.addSessionListener(myI2PSessionListener, I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY);
+ * session.sendMessage(dest, payload, I2PSession.PROTO_DATAGRAM, fromPort, toPort);
+ *
+ * Multiple streaming ports:
+ * Needs some streaming lib hacking
+ *
+ * @author zzz
+ */
+class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
+ private I2PSessionDemultiplexer _demultiplexer;
+
+ public I2PSessionMuxedImpl(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
+ super(ctx, destKeyStream, options);
+ // also stored in _sessionListener but we keep it in _demultipexer
+ // as well so we don't have to keep casting
+ _demultiplexer = new I2PSessionDemultiplexer(ctx);
+ super.setSessionListener(_demultiplexer);
+ // discards the one in super(), sorry about that... (no it wasn't started yet)
+ _availabilityNotifier = new MuxedAvailabilityNotifier();
+ }
+
+ /** listen on all protocols and ports */
+ @Override
+ public void setSessionListener(I2PSessionListener lsnr) {
+ _demultiplexer.addListener(lsnr, PROTO_ANY, PORT_ANY);
+ }
+
+ /**
+ * Listen on specified protocol and port.
+ *
+ * An existing listener with the same proto and port is replaced.
+ * Only the listener with the best match is called back for each message.
+ *
+ * @param proto 1-254 or PROTO_ANY for all; recommended:
+ * I2PSession.PROTO_STREAMING
+ * I2PSession.PROTO_DATAGRAM
+ * 255 disallowed
+ * @param port 1-65535 or PORT_ANY for all
+ */
+ public void addSessionListener(I2PSessionListener lsnr, int proto, int port) {
+ _demultiplexer.addListener(lsnr, proto, port);
+ }
+
+ /**
+ * Listen on specified protocol and port, and receive notification
+ * of proto, fromPort, and toPort for every message.
+ * @param proto 1-254 or 0 for all; 255 disallowed
+ * @param port 1-65535 or 0 for all
+ */
+ public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) {
+ _demultiplexer.addMuxedListener(l, proto, port);
+ }
+
+ /** removes the specified listener (only) */
+ public void removeListener(int proto, int port) {
+ _demultiplexer.removeListener(proto, port);
+ }
+
+ @Override
+ public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
+ return sendMessage(dest, payload, 0, payload.length, null, null,
+ 0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED);
+ }
+
+ @Override
+ public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException {
+ return sendMessage(dest, payload, 0, payload.length, null, null, 0, proto, fromport, toport);
+ }
+
+ @Override
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
+ SessionKey keyUsed, Set tagsSent, long expires)
+ throws I2PSessionException {
+ return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED);
+ }
+
+ @Override
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
+ int proto, int fromport, int toport) throws I2PSessionException {
+ return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, proto, fromport, toport);
+ }
+
+ /**
+ * @param proto 1-254 or 0 for unset; recommended:
+ * I2PSession.PROTO_UNSPECIFIED
+ * I2PSession.PROTO_STREAMING
+ * I2PSession.PROTO_DATAGRAM
+ * 255 disallowed
+ * @param fromPort 1-65535 or 0 for unset
+ * @param toPort 1-65535 or 0 for unset
+ */
+ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
+ SessionKey keyUsed, Set tagsSent, long expires,
+ int proto, int fromPort, int toPort)
+ throws I2PSessionException {
+ if (isClosed()) throw new I2PSessionException("Already closed");
+ updateActivity();
+
+ boolean sc = shouldCompress(size);
+ if (sc)
+ payload = DataHelper.compress(payload, offset, size);
+ else
+ payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION);
+
+ setProto(payload, proto);
+ setFromPort(payload, fromPort);
+ setToPort(payload, toPort);
+
+ _context.statManager().addRateData("i2cp.tx.msgCompressed", payload.length, 0);
+ _context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0);
+ return sendBestEffort(dest, payload, keyUsed, tagsSent, expires);
+ }
+
+ /**
+ * Receive a payload message and let the app know its available
+ */
+ @Override
+ public void addNewMessage(MessagePayloadMessage msg) {
+ Long mid = new Long(msg.getMessageId());
+ _availableMessages.put(mid, msg);
+ long id = msg.getMessageId();
+ byte data[] = msg.getPayload().getUnencryptedData();
+ if ((data == null) || (data.length <= 0)) {
+ if (_log.shouldLog(Log.CRIT))
+ _log.log(Log.CRIT, getPrefix() + "addNewMessage of a message with no unencrypted data",
+ new Exception("Empty message"));
+ return;
+ }
+ int size = data.length;
+ if (size < 10) {
+ _log.error(getPrefix() + "length too short for gzip header: " + size);
+ return;
+ }
+ ((MuxedAvailabilityNotifier)_availabilityNotifier).available(id, size, getProto(msg),
+ getFromPort(msg), getToPort(msg));
+ SimpleScheduler.getInstance().addEvent(new VerifyUsage(mid), 30*1000);
+ }
+
+ protected class MuxedAvailabilityNotifier extends AvailabilityNotifier {
+ private LinkedBlockingQueue _msgs;
+ private boolean _alive;
+ private static final int POISON_SIZE = -99999;
+
+ public MuxedAvailabilityNotifier() {
+ _msgs = new LinkedBlockingQueue();
+ }
+
+ public void stopNotifying() {
+ _msgs.clear();
+ if (_alive) {
+ _alive = false;
+ try {
+ _msgs.put(new MsgData(0, POISON_SIZE, 0, 0, 0));
+ } catch (InterruptedException ie) {}
+ }
+ }
+
+ /** unused */
+ public void available(long msgId, int size) { throw new IllegalArgumentException("no"); }
+
+ public void available(long msgId, int size, int proto, int fromPort, int toPort) {
+ try {
+ _msgs.put(new MsgData((int)(msgId & 0xffffffff), size, proto, fromPort, toPort));
+ } catch (InterruptedException ie) {}
+ }
+
+ public void run() {
+ _alive = true;
+ while (true) {
+ MsgData msg;
+ try {
+ msg = _msgs.take();
+ } catch (InterruptedException ie) {
+ continue;
+ }
+ if (msg.size == POISON_SIZE)
+ break;
+ try {
+ _demultiplexer.messageAvailable(I2PSessionMuxedImpl.this, msg.id,
+ msg.size, msg.proto, msg.fromPort, msg.toPort);
+ } catch (Exception e) {
+ _log.error("Error notifying app of message availability");
+ }
+ }
+ }
+ }
+
+ /** let's keep this simple */
+ private static class MsgData {
+ public int id, size, proto, fromPort, toPort;
+ public MsgData(int i, int s, int p, int f, int t) {
+ id = i;
+ size = s;
+ proto = p;
+ fromPort = f;
+ toPort = t;
+ }
+ }
+
+ /**
+ * No, we couldn't put any protocol byte in front of everything and
+ * keep backward compatibility. But there are several bytes that
+ * are unused AND unchecked in the gzip header in releases <= 0.7.
+ * So let's use 5 of them for a protocol and two 2-byte ports.
+ *
+ * Following are all the methods to hide the
+ * protocol, fromPort, and toPort in the gzip header
+ *
+ * The fields used are all ignored on receive in ResettableGzipInputStream
+ *
+ * See also ResettableGzipOutputStream.
+ * Ref: RFC 1952
+ *
+ */
+
+ /** OS byte in gzip header */
+ private static final int PROTO_BYTE = 9;
+
+ /** Upper two bytes of MTIME in gzip header */
+ private static final int FROMPORT_BYTES = 4;
+
+ /** Lower two bytes of MTIME in gzip header */
+ private static final int TOPORT_BYTES = 6;
+
+ /** Non-muxed sets the OS byte to 0xff */
+ private static int getProto(MessagePayloadMessage msg) {
+ int rv = getByte(msg, PROTO_BYTE) & 0xff;
+ return rv == 0xff ? PROTO_UNSPECIFIED : rv;
+ }
+
+ /** Non-muxed sets the MTIME bytes to 0 */
+ private static int getFromPort(MessagePayloadMessage msg) {
+ return (((getByte(msg, FROMPORT_BYTES) & 0xff) << 8) |
+ (getByte(msg, FROMPORT_BYTES + 1) & 0xff));
+ }
+
+ /** Non-muxed sets the MTIME bytes to 0 */
+ private static int getToPort(MessagePayloadMessage msg) {
+ return (((getByte(msg, TOPORT_BYTES) & 0xff) << 8) |
+ (getByte(msg, TOPORT_BYTES + 1) & 0xff));
+ }
+
+ private static int getByte(MessagePayloadMessage msg, int i) {
+ return msg.getPayload().getUnencryptedData()[i] & 0xff;
+ }
+
+ private static void setProto(byte[] payload, int p) {
+ payload[PROTO_BYTE] = (byte) (p & 0xff);
+ }
+
+ private static void setFromPort(byte[] payload, int p) {
+ payload[FROMPORT_BYTES] = (byte) ((p >> 8) & 0xff);
+ payload[FROMPORT_BYTES + 1] = (byte) (p & 0xff);
+ }
+
+ private static void setToPort(byte[] payload, int p) {
+ payload[TOPORT_BYTES] = (byte) ((p >> 8) & 0xff);
+ payload[TOPORT_BYTES + 1] = (byte) (p & 0xff);
+ }
+}
diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedListener.java b/core/java/src/net/i2p/client/I2PSessionMuxedListener.java
new file mode 100644
index 0000000000..118dc75cae
--- /dev/null
+++ b/core/java/src/net/i2p/client/I2PSessionMuxedListener.java
@@ -0,0 +1,62 @@
+package net.i2p.client;
+
+/*
+ * public domain
+ */
+
+/**
+ * Define a means for the router to asynchronously notify the client that a
+ * new message is available or the router is under attack.
+ *
+ * @author zzz extends I2PSessionListener
+ */
+public interface I2PSessionMuxedListener extends I2PSessionListener {
+
+ /**
+ * Will be called only if you register via
+ * setSessionListener() or addSessionListener().
+ * And if you are doing that, just use I2PSessionListener.
+ *
+ * If you register via addSessionListener(),
+ * this will be called only for the proto(s) and toport(s) you register for.
+ *
+ * @param session session to notify
+ * @param msgId message number available
+ * @param size size of the message - why it's a long and not an int is a mystery
+ */
+ void messageAvailable(I2PSession session, int msgId, long size);
+
+ /**
+ * Instruct the client that the given session has received a message
+ *
+ * Will be called only if you register via addMuxedSessionListener().
+ * Will be called only for the proto(s) and toport(s) you register for.
+ *
+ * @param session session to notify
+ * @param msgId message number available
+ * @param size size of the message - why it's a long and not an int is a mystery
+ * @param proto 1-254 or 0 for unspecified
+ * @param fromport 1-65535 or 0 for unspecified
+ * @param toport 1-65535 or 0 for unspecified
+ */
+ void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport);
+
+ /** Instruct the client that the session specified seems to be under attack
+ * and that the client may wish to move its destination to another router.
+ * @param session session to report abuse to
+ * @param severity how bad the abuse is
+ */
+ void reportAbuse(I2PSession session, int severity);
+
+ /**
+ * Notify the client that the session has been terminated
+ *
+ */
+ void disconnected(I2PSession session);
+
+ /**
+ * Notify the client that some error occurred
+ *
+ */
+ void errorOccurred(I2PSession session, String message, Throwable error);
+}
diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java
index fcfafe7672..b417bd7f7a 100644
--- a/core/java/src/net/i2p/client/I2PSimpleSession.java
+++ b/core/java/src/net/i2p/client/I2PSimpleSession.java
@@ -17,25 +17,32 @@ import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
+import net.i2p.data.i2cp.BandwidthLimitsMessage;
import net.i2p.data.i2cp.DestLookupMessage;
import net.i2p.data.i2cp.DestReplyMessage;
+import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
- * Create a new session for doing naming queries only. Do not create a Destination.
+ * Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
* Don't create a producer. Do not send/receive messages to other Destinations.
* Cannot handle multiple simultaneous queries atm.
* Could be expanded to ask the router other things.
+ *
+ * @author zzz
*/
class I2PSimpleSession extends I2PSessionImpl2 {
private boolean _destReceived;
private Object _destReceivedLock;
private Destination _destination;
+ private boolean _bwReceived;
+ private Object _bwReceivedLock;
+ private int[] _bwLimits;
/**
- * Create a new session for doing naming queries only. Do not create a destination.
+ * Create a new session for doing naming and bandwidth queries only. Do not create a destination.
*
* @throws I2PSessionException if there is a problem
*/
@@ -94,6 +101,14 @@ class I2PSimpleSession extends I2PSessionImpl2 {
}
}
+ void bwReceived(int[] i) {
+ _bwReceived = true;
+ _bwLimits = i;
+ synchronized (_bwReceivedLock) {
+ _bwReceivedLock.notifyAll();
+ }
+ }
+
public Destination lookupDest(Hash h) throws I2PSessionException {
if (_closed)
return null;
@@ -110,14 +125,31 @@ class I2PSimpleSession extends I2PSessionImpl2 {
return _destination;
}
+ public int[] bandwidthLimits() throws I2PSessionException {
+ if (_closed)
+ return null;
+ _bwReceivedLock = new Object();
+ sendMessage(new GetBandwidthLimitsMessage());
+ for (int i = 0; i < 5 && !_bwReceived; i++) {
+ try {
+ synchronized (_bwReceivedLock) {
+ _bwReceivedLock.wait(1000);
+ }
+ } catch (InterruptedException ie) {}
+ }
+ _bwReceived = false;
+ return _bwLimits;
+ }
+
/**
* Only map message handlers that we will use
*/
class SimpleMessageHandlerMap extends I2PClientMessageHandlerMap {
public SimpleMessageHandlerMap(I2PAppContext context) {
- int highest = DestReplyMessage.MESSAGE_TYPE;
+ int highest = Math.max(DestReplyMessage.MESSAGE_TYPE, BandwidthLimitsMessage.MESSAGE_TYPE);
_handlers = new I2CPMessageHandler[highest+1];
_handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
+ _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
}
}
}
diff --git a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java
index 7a8bd200e1..e662f65727 100644
--- a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java
+++ b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java
@@ -79,7 +79,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
leaseSet.setEncryptionKey(li.getPublicKey());
leaseSet.setSigningKey(li.getSigningPublicKey());
- boolean encrypt = Boolean.valueOf(session.getOptions().getProperty("i2cp.encryptLeaseset")).booleanValue();
+ boolean encrypt = Boolean.valueOf(session.getOptions().getProperty("i2cp.encryptLeaseSet")).booleanValue();
String sk = session.getOptions().getProperty("i2cp.leaseSetKey");
if (encrypt && sk != null) {
SessionKey key = new SessionKey();
diff --git a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
index 9fa227f817..054bd9d8f1 100644
--- a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
+++ b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
@@ -16,8 +16,10 @@ import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
+import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
+import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
@@ -135,4 +137,34 @@ public class HostsTxtNamingService extends NamingService {
}
return null;
}
+
+ @Override
+ public String reverseLookup(Hash h) {
+ List filenames = getFilenames();
+ for (int i = 0; i < filenames.size(); i++) {
+ String hostsfile = (String)filenames.get(i);
+ Properties hosts = new Properties();
+ try {
+ File f = new File(hostsfile);
+ if ( (f.exists()) && (f.canRead()) ) {
+ DataHelper.loadProps(hosts, f, true);
+ Set keyset = hosts.keySet();
+ Iterator iter = keyset.iterator();
+ while (iter.hasNext()) {
+ String host = (String)iter.next();
+ String key = hosts.getProperty(host);
+ try {
+ Destination destkey = new Destination();
+ destkey.fromBase64(key);
+ if (h.equals(destkey.calculateHash()))
+ return host;
+ } catch (DataFormatException dfe) {}
+ }
+ }
+ } catch (Exception ioe) {
+ _log.error("Error loading hosts file " + hostsfile, ioe);
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java
index 5b61b1bcf8..ee02ec9111 100644
--- a/core/java/src/net/i2p/client/naming/NamingService.java
+++ b/core/java/src/net/i2p/client/naming/NamingService.java
@@ -16,6 +16,7 @@ import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
+import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
@@ -61,6 +62,7 @@ public abstract class NamingService {
* null if no reverse lookup is possible.
*/
public abstract String reverseLookup(Destination dest);
+ public String reverseLookup(Hash h) { return null; };
/**
* Check if host name is valid Base64 encoded dest and return this
diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java
index dfd055382d..06e37544b3 100644
--- a/core/java/src/net/i2p/crypto/TrustedUpdate.java
+++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java
@@ -108,6 +108,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
private Log _log;
private ArrayList _trustedKeys;
+ private String _newVersion;
/**
* Constructs a new TrustedUpdate with the default global
@@ -127,6 +128,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
_context = context;
_log = _context.logManager().getLog(TrustedUpdate.class);
_trustedKeys = new ArrayList();
+ _newVersion = null;
String propertyTrustedKeys = context.getProperty(PROP_TRUSTED_KEYS);
@@ -379,6 +381,11 @@ D8usM7Dxp5yrDrCYZ5AIijc=
}
}
+ /** version in the .sud file, valid only after calling migrateVerified() */
+ public String newVersion() {
+ return _newVersion;
+ }
+
/**
* Verifies that the version of the given signed update file is newer than
* currentVersion.
@@ -390,10 +397,8 @@ D8usM7Dxp5yrDrCYZ5AIijc=
* than the current version, otherwise false.
*/
public boolean isUpdatedVersion(String currentVersion, String signedFile) {
- if (needsUpdate(currentVersion, getVersionString(signedFile)))
- return true;
- else
- return false;
+ _newVersion = getVersionString(signedFile);
+ return needsUpdate(currentVersion, getVersionString(signedFile));
}
/**
diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java
index d9e52aecc0..b5d68ee411 100644
--- a/core/java/src/net/i2p/data/PrivateKeyFile.java
+++ b/core/java/src/net/i2p/data/PrivateKeyFile.java
@@ -261,7 +261,7 @@ public class PrivateKeyFile {
public String toString() {
StringBuffer s = new StringBuffer(128);
s.append("Dest: ");
- s.append(this.dest.toBase64());
+ s.append(this.dest != null ? this.dest.toBase64() : "null");
s.append("\nContains: ");
s.append(this.dest);
s.append("\nPrivate Key: ");
diff --git a/core/java/src/net/i2p/data/i2cp/BandwidthLimitsMessage.java b/core/java/src/net/i2p/data/i2cp/BandwidthLimitsMessage.java
new file mode 100644
index 0000000000..2f8d567869
--- /dev/null
+++ b/core/java/src/net/i2p/data/i2cp/BandwidthLimitsMessage.java
@@ -0,0 +1,101 @@
+package net.i2p.data.i2cp;
+
+/*
+ * public domain
+ *
+ */
+
+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.util.Log;
+
+/**
+ * Tell the other side the limits
+ *
+ * @author zzz
+ */
+public class BandwidthLimitsMessage extends I2CPMessageImpl {
+ private final static Log _log = new Log(BandwidthLimitsMessage.class);
+ public final static int MESSAGE_TYPE = 23;
+ private static final int LIMITS = 16;
+ private int[] data;
+
+ public BandwidthLimitsMessage() {
+ super();
+ data = new int[LIMITS];
+ }
+
+ /**
+ * Let's define it this way.
+ * Leave some extra. This is only local and rarely sent so we don't care about waste.
+ *
+ * 0) Client inbound limit (KBps)
+ * 1) Client outbound limit (KBps)
+ * 2) Router inbound limit (KBps)
+ * 3) Router inbound burst limit (KBps)
+ * 4) Router outbound limit (KBps)
+ * 5) Router outbound burst limit (KBps)
+ * 6) Router burst time (seconds)
+ * 7-15) undefined
+ */
+ public BandwidthLimitsMessage(int in, int out) {
+ this();
+ data[0] = in;
+ data[1] = out;
+ }
+
+ public int[] getLimits() {
+ return data;
+ }
+
+ @Override
+ protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
+ try {
+ for (int i = 0; i < LIMITS; i++) {
+ data[i] = (int) DataHelper.readLong(in, 4);
+ }
+ } catch (DataFormatException dfe) {
+ throw new I2CPMessageException("Unable to load the message data", dfe);
+ }
+ }
+
+ @Override
+ protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(64);
+ try {
+ for (int i = 0; i < LIMITS; i++) {
+ DataHelper.writeLong(os, 4, data[i]);
+ }
+ } catch (DataFormatException dfe) {
+ throw new I2CPMessageException("Error writing out the message data", dfe);
+ }
+ return os.toByteArray();
+ }
+
+ public int getType() {
+ return MESSAGE_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof BandwidthLimitsMessage)) {
+ BandwidthLimitsMessage msg = (BandwidthLimitsMessage) object;
+ return DataHelper.eq(data, msg.getLimits());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("[BandwidthLimitsMessage");
+ buf.append("\n\tIn: ").append(data[0]);
+ buf.append("\n\tOut: ").append(data[1]);
+ buf.append("]");
+ return buf.toString();
+ }
+}
diff --git a/core/java/src/net/i2p/data/i2cp/GetBandwidthLimitsMessage.java b/core/java/src/net/i2p/data/i2cp/GetBandwidthLimitsMessage.java
new file mode 100644
index 0000000000..a47f883d78
--- /dev/null
+++ b/core/java/src/net/i2p/data/i2cp/GetBandwidthLimitsMessage.java
@@ -0,0 +1,56 @@
+package net.i2p.data.i2cp;
+
+/*
+ * public domain
+ *
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.i2p.util.Log;
+
+/**
+ * Request the router tells us the current bw limits
+ *
+ * @author zzz
+ */
+public class GetBandwidthLimitsMessage extends I2CPMessageImpl {
+ private final static Log _log = new Log(GetBandwidthLimitsMessage.class);
+ public final static int MESSAGE_TYPE = 8;
+
+ public GetBandwidthLimitsMessage() {
+ super();
+ }
+
+ @Override
+ protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
+ // noop
+ }
+
+ @Override
+ protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
+ byte rv[] = new byte[0];
+ return rv;
+ }
+
+ public int getType() {
+ return MESSAGE_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof GetBandwidthLimitsMessage)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("[GetBandwidthLimitsMessage]");
+ return buf.toString();
+ }
+}
diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
index 61d8650534..04da546f78 100644
--- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
+++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
@@ -94,6 +94,10 @@ public class I2CPMessageHandler {
return new DestLookupMessage();
case DestReplyMessage.MESSAGE_TYPE:
return new DestReplyMessage();
+ case GetBandwidthLimitsMessage.MESSAGE_TYPE:
+ return new GetBandwidthLimitsMessage();
+ case BandwidthLimitsMessage.MESSAGE_TYPE:
+ return new BandwidthLimitsMessage();
default:
throw new I2CPMessageException("The type " + type + " is an unknown I2CP message");
}
diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
index 13b01a67ad..79066c4c62 100644
--- a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
+++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
@@ -178,7 +178,11 @@ public class I2CPMessageReader {
// pause .5 secs when we're paused
try {
Thread.sleep(500);
- } catch (InterruptedException ie) { // nop
+ } catch (InterruptedException ie) {
+ // we should break away here.
+ _log.warn("Breaking away stream", ie);
+ _listener.disconnected(I2CPMessageReader.this);
+ cancelRunner();
}
}
}
diff --git a/core/java/src/net/i2p/stat/RateStat.java b/core/java/src/net/i2p/stat/RateStat.java
index 329706040c..dd1fe63e3f 100644
--- a/core/java/src/net/i2p/stat/RateStat.java
+++ b/core/java/src/net/i2p/stat/RateStat.java
@@ -134,7 +134,6 @@ public class RateStat {
buf.append("# Period : ").append(DataHelper.formatDuration(_rates[i].getPeriod())).append(" for rate ")
.append(_groupName).append(" - ").append(_statName).append(NL);
buf.append(NL);
- out.write(buf.toString().getBytes());
String curPrefix = prefix + "." + DataHelper.formatDuration(_rates[i].getPeriod());
_rates[i].store(curPrefix, buf);
out.write(buf.toString().getBytes());
diff --git a/core/java/src/net/i2p/stat/StatManager.java b/core/java/src/net/i2p/stat/StatManager.java
index 4c5c69c791..56af55f71d 100644
--- a/core/java/src/net/i2p/stat/StatManager.java
+++ b/core/java/src/net/i2p/stat/StatManager.java
@@ -1,5 +1,6 @@
package net.i2p.stat;
+import java.text.Collator;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -178,7 +179,7 @@ public class StatManager {
/** Group name (String) to a Set of stat names, ordered alphabetically */
public Map getStatsByGroup() {
- Map groups = new TreeMap();
+ Map groups = new TreeMap(Collator.getInstance());
for (Iterator iter = _frequencyStats.values().iterator(); iter.hasNext();) {
FrequencyStat stat = (FrequencyStat) iter.next();
if (!groups.containsKey(stat.getGroupName())) groups.put(stat.getGroupName(), new TreeSet());
diff --git a/core/java/src/net/i2p/util/ConvertToHash.java b/core/java/src/net/i2p/util/ConvertToHash.java
new file mode 100644
index 0000000000..28da87d217
--- /dev/null
+++ b/core/java/src/net/i2p/util/ConvertToHash.java
@@ -0,0 +1,79 @@
+package net.i2p.util;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Base32;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.data.Hash;
+
+/**
+ * Convert any kind of destination String to a hash
+ * Supported:
+ * Base64 dest
+ * Base64 dest.i2p
+ * Base64 Hash
+ * Base32 Hash
+ * Base32 desthash.b32.i2p
+ * example.i2p
+ *
+ * @author zzz
+ */
+public class ConvertToHash {
+
+ /**
+ * Convert any kind of destination String to a hash
+ *
+ * @return null on failure
+ */
+ public static Hash getHash(String peer) {
+ if (peer == null)
+ return null;
+ Hash h = new Hash();
+ String peerLC = peer.toLowerCase();
+ // b64 hash
+ if (peer.length() == 44 && !peerLC.endsWith(".i2p")) {
+ try {
+ h.fromBase64(peer);
+ } catch (DataFormatException dfe) {}
+ }
+ // b64 dest.i2p
+ if (h.getData() == null && peer.length() >= 520 && peerLC.endsWith(".i2p")) {
+ try {
+ Destination d = new Destination();
+ d.fromBase64(peer.substring(0, peer.length() - 4));
+ h = d.calculateHash();
+ } catch (DataFormatException dfe) {}
+ }
+ // b64 dest
+ if (h.getData() == null && peer.length() >= 516 && !peerLC.endsWith(".i2p")) {
+ try {
+ Destination d = new Destination();
+ d.fromBase64(peer);
+ h = d.calculateHash();
+ } catch (DataFormatException dfe) {}
+ }
+ // b32 hash.b32.i2p
+ // do this here rather than in naming service so it will work
+ // even if the leaseset is not found
+ if (h.getData() == null && peer.length() == 60 && peerLC.endsWith(".b32.i2p")) {
+ byte[] b = Base32.decode(peer.substring(0, 52));
+ if (b != null && b.length == Hash.HASH_LENGTH)
+ h.setData(b);
+ }
+ // b32 hash
+ if (h.getData() == null && peer.length() == 52 && !peerLC.endsWith(".i2p")) {
+ byte[] b = Base32.decode(peer);
+ if (b != null && b.length == Hash.HASH_LENGTH)
+ h.setData(b);
+ }
+ // example.i2p
+ if (h.getData() == null) {
+ Destination d = I2PAppContext.getGlobalContext().namingService().lookup(peer);
+ if (d != null)
+ h = d.calculateHash();
+ }
+ if (h.getData() == null)
+ return null;
+ return h;
+ }
+}
diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index 4abdc6fd4d..5f0e8d5e9f 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -18,7 +18,7 @@ import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
/**
- * EepGet [-p localhost:4444]
+ * EepGet [-p 127.0.0.1:4444]
* [-n #retries]
* [-o outputFile]
* [-m markSize lineLen]
@@ -123,11 +123,11 @@ public class EepGet {
}
/**
- * EepGet [-p localhost:4444] [-n #retries] [-e etag] [-o outputFile] [-m markSize lineLen] url
+ * EepGet [-p 127.0.0.1:4444] [-n #retries] [-e etag] [-o outputFile] [-m markSize lineLen] url
*
*/
public static void main(String args[]) {
- String proxyHost = "localhost";
+ String proxyHost = "127.0.0.1";
int proxyPort = 4444;
int numRetries = 5;
int markSize = 1024;
@@ -212,7 +212,7 @@ public class EepGet {
}
private static void usage() {
- System.err.println("EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] [-t timeout] url");
+ System.err.println("EepGet [-p 127.0.0.1:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] [-t timeout] url");
}
public static interface StatusListener {
diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java
index 7a64e24e42..970de52c85 100644
--- a/core/java/src/net/i2p/util/NativeBigInteger.java
+++ b/core/java/src/net/i2p/util/NativeBigInteger.java
@@ -23,6 +23,9 @@ import freenet.support.CPUInformation.CPUInfo;
import freenet.support.CPUInformation.IntelCPUInfo;
import freenet.support.CPUInformation.UnknownCPUException;
+import net.i2p.I2PAppContext;
+import net.i2p.util.Log;
+
/**
*
BigInteger that takes advantage of the jbigi library for the modPow operation,
* which accounts for a massive segment of the processing cost of asymmetric
@@ -89,6 +92,9 @@ public class NativeBigInteger extends BigInteger {
* do we want to dump some basic success/failure info to stderr during
* initialization? this would otherwise use the Log component, but this makes
* it easier for other systems to reuse this class
+ *
+ * Well, we really want to use Log so if you are one of those "other systems"
+ * then comment out the I2PAppContext usage below.
*/
private static final boolean _doLog = System.getProperty("jbigi.dontLog") == null;
@@ -401,38 +407,32 @@ public class NativeBigInteger extends BigInteger {
boolean loaded = loadGeneric("jbigi");
if (loaded) {
_nativeOk = true;
- if (_doLog)
- System.err.println("INFO: Locally optimized native BigInteger loaded from the library path");
+ info("Locally optimized native BigInteger library loaded from the library path");
} else {
loaded = loadFromResource("jbigi");
if (loaded) {
_nativeOk = true;
- if (_doLog)
- System.err.println("INFO: Locally optimized native BigInteger loaded from resource");
+ info("Locally optimized native BigInteger library loaded from resource");
} else {
loaded = loadFromResource(true);
if (loaded) {
_nativeOk = true;
- if (_doLog)
- System.err.println("INFO: Optimized native BigInteger library '"+getResourceName(true)+"' loaded from resource");
+ info("Optimized native BigInteger library '"+getResourceName(true)+"' loaded from resource");
} else {
loaded = loadGeneric(true);
if (loaded) {
_nativeOk = true;
- if (_doLog)
- System.err.println("INFO: Optimized native BigInteger library '"+getMiddleName(true)+"' loaded from somewhere in the path");
+ info("Optimized native BigInteger library '"+getMiddleName(true)+"' loaded from somewhere in the path");
} else {
loaded = loadFromResource(false);
if (loaded) {
_nativeOk = true;
- if (_doLog)
- System.err.println("INFO: Non-optimized native BigInteger library '"+getResourceName(false)+"' loaded from resource");
+ info("Non-optimized native BigInteger library '"+getResourceName(false)+"' loaded from resource");
} else {
loaded = loadGeneric(false);
if (loaded) {
_nativeOk = true;
- if (_doLog)
- System.err.println("INFO: Non-optimized native BigInteger library '"+getMiddleName(false)+"' loaded from somewhere in the path");
+ info("Non-optimized native BigInteger library '"+getMiddleName(false)+"' loaded from somewhere in the path");
} else {
_nativeOk = false;
}
@@ -442,16 +442,27 @@ public class NativeBigInteger extends BigInteger {
}
}
}
- if (_doLog && !_nativeOk)
- System.err.println("INFO: Native BigInteger library jbigi not loaded - using pure java");
+ if (!_nativeOk) {
+ warn("Native BigInteger library jbigi not loaded - using pure Java - " +
+ "poor performance may result - see http://www.i2p2.i2p/jbigi.html for help");
+ }
}catch(Exception e){
- if (_doLog) {
- System.err.println("INFO: Native BigInteger library jbigi not loaded, reason: '"+e.getMessage()+"' - using pure java");
- e.printStackTrace();
- }
+ warn("Native BigInteger library jbigi not loaded, reason: '"+e.getMessage()+"' - using pure java");
}
}
+ private static void info(String s) {
+ if(_doLog)
+ System.err.println("INFO: " + s);
+ I2PAppContext.getGlobalContext().logManager().getLog(NativeBigInteger.class).info(s);
+ }
+
+ private static void warn(String s) {
+ if(_doLog)
+ System.err.println("WARNING: " + s);
+ I2PAppContext.getGlobalContext().logManager().getLog(NativeBigInteger.class).warn(s);
+ }
+
/**
*
Try loading it from an explictly build jbigi.dll / libjbigi.so first, before
* looking into a jbigi.jar for any other libraries.
diff --git a/core/java/src/net/i2p/util/SimpleScheduler.java b/core/java/src/net/i2p/util/SimpleScheduler.java
index becf100995..ee7d36e99a 100644
--- a/core/java/src/net/i2p/util/SimpleScheduler.java
+++ b/core/java/src/net/i2p/util/SimpleScheduler.java
@@ -1,7 +1,6 @@
package net.i2p.util;
import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadFactory;
@@ -43,6 +42,7 @@ public class SimpleScheduler {
_name = name;
_count = 0;
_executor = new ScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory());
+ _executor.prestartAllCoreThreads();
}
/**
@@ -91,6 +91,11 @@ public class SimpleScheduler {
public Thread newThread(Runnable r) {
Thread rv = Executors.defaultThreadFactory().newThread(r);
rv.setName(_name + ' ' + (++_count) + '/' + THREADS);
+// Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates!
+// String name = rv.getThreadGroup().getName();
+// if(!name.equals("main")) {
+// (new Exception("OWCH! DAMN! Wrong ThreadGroup `" + name +"', `" + rv.getName() + "'")).printStackTrace();
+// }
rv.setDaemon(true);
return rv;
}
@@ -144,9 +149,11 @@ public class SimpleScheduler {
_timeoutMs = timeoutMs;
_scheduled = initialDelay + System.currentTimeMillis();
}
+ @Override
public void schedule() {
_executor.scheduleWithFixedDelay(this, _initialDelay, _timeoutMs, TimeUnit.MILLISECONDS);
}
+ @Override
public void run() {
super.run();
_scheduled = _timeoutMs + System.currentTimeMillis();
diff --git a/core/java/src/net/i2p/util/SimpleTimer2.java b/core/java/src/net/i2p/util/SimpleTimer2.java
index 6239ed42f2..b2af33cf2b 100644
--- a/core/java/src/net/i2p/util/SimpleTimer2.java
+++ b/core/java/src/net/i2p/util/SimpleTimer2.java
@@ -5,7 +5,6 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadFactory;
-import java.util.Map;
import net.i2p.I2PAppContext;
@@ -42,6 +41,7 @@ public class SimpleTimer2 {
_name = name;
_count = 0;
_executor = new CustomScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory());
+ _executor.prestartAllCoreThreads();
}
/**
@@ -56,6 +56,7 @@ public class SimpleTimer2 {
super(threads, factory);
}
+ @Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) // shoudn't happen, caught in RunnableEvent.run()
@@ -67,6 +68,11 @@ public class SimpleTimer2 {
public Thread newThread(Runnable r) {
Thread rv = Executors.defaultThreadFactory().newThread(r);
rv.setName(_name + ' ' + (++_count) + '/' + THREADS);
+// Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates!
+// String name = rv.getThreadGroup().getName();
+// if(!name.equals("main")) {
+// (new Exception("OWCH! DAMN! Wrong ThreadGroup `" + name +"', `" + rv.getName() + "'")).printStackTrace();
+// }
rv.setDaemon(true);
return rv;
}
@@ -232,6 +238,7 @@ public class SimpleTimer2 {
public abstract void timeReached();
}
+ @Override
public String toString() {
return _name;
}
diff --git a/history.txt b/history.txt
index 62c4430e91..ff522acbd5 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,207 @@
+2009-04-17 sponge
+ * fixed setIP, just be sure to distclean before building :-)
+ * more lint taken care of as well.
+
+2009-04-17 sponge
+ * setIP wants to be a static method in the class, but it produces
+ warnings about it being static from other code.
+
+2009-04-17 sponge
+ * Catch NPE in NTCP.
+ This possibly augments fix 2009-04-11 welterde below.
+ * Various LINT on NTCP sources, and removal of space-wasting
+ spaces at end of lines in sources touched.
+
+2009-04-13 Mathiasdm
+ * Bugfix on tray icon updating
+ * Some more work on the general configuration menu
+ (currently not added to the tray icon menu yet, needs more work)
+ * Tweaked the desktopgui logo
+
+2009-04-13 Mathiasdm
+ * Added I2P version and GUI version to desktopgui
+ * Tweaks to the tray icon menu
+ * Some starting work on a GUI general configuration menu
+ * Bugfix allowing spaces in directory structure
+
+2009-04-13 welterde
+ * small fix in the eepproxy
+
+2009-04-11 welterde
+ * fixed NPE in NTCP transport
+
+2009-04-11 sponge
+ * i2ptunnel janitorial work and fixes on most locks.
+ Some locks still need work, and are marked with LINT in the comment.
+ Just grep for "LINT" to see where the remaining places are.
+
+2009-04-10 sponge
+ * More BOB threadgroup fixes, plus debug dump when things go wrong.
+ * Fixes to streaminglib, I2CP, which are related to the TG problem.
+ * JavaDocs fixups.
+
+2009-04-08 sponge
+ * More hopeful fixups to the infamous orpahned tunnel problem. *Sigh*
+
+2009-04-08 zzz
+ * IPV6/localhost:
+ - Enable IPv6 stack in the JVM, hopefully won't break anything
+ - Patch Jetty to support binding to IPv6 addresses
+ - Allow multiple bind addresses for the router console
+ in the clients.config file; for new installs the
+ default is now "127.0.0.1,::1"
+ - Change most instances of "localhost" to "127.0.0.1"
+ throughout the code
+ * Router:
+ - Move some classes to private static inner
+
+2009-04-07 sponge
+ * BOB prevent jvac from optimizing out thread-group code from -10
+
+2009-04-07 zzz
+ * NTCP: Prevent occasional NPE introduced in -4
+ * streamr: Synchronize DatagramMaker
+
+2009-04-07 sponge
+ * SimpleTimer2, SimpleScheduler fixed so that the threads all run from
+ The main threadgroup, not in the current possible child threadgroup.
+ So long as any SimpleTimer2/SimpleScheduler is started *BEFORE* any
+ child threadgroups, the constructors are threadgroup safe. What would
+ be super cool is if they were to be all jailed within thier very own
+ threadgroup too, but, I2P isn't up to the task of this yet.
+ * Fixes to BOB to ensure the above is true.
+
+2009-04-06 sponge
+ * Debugging to make SimpleTimer2 and SimpleScheduler easier to debug.
+ * Fix for the config files in the GUI from mathiasdm
+
+2009-04-04 sponge
+ * Hopeful fixups to the infamous orpahned tunnel problem.
+ * BOB now 0.0.5
+
+2009-04-04 zzz
+ * NTCP: Don't bid on messages too big to handle
+
+2009-04-03 zzz
+ * Console:
+ - Fix bug with IE buttons not working,
+ because it sends the label instead of the value
+ - Display version of downloaded update
+ * Update:
+ - Change default to "Download and verify"
+ - Change news fetch default to 24h (was 12h)
+
+2009-04-03 sponge
+ * Fix broken dependencies for BOB.jar
+ * Router build version incremented to 5.
+
+2009-04-02 zzz
+ * Profiles:
+ - Remove unused calculators and RateStats:
+ CapacityCalculator, StrictSpeedCalculator, IsFailingCalculator;
+ sendFailureSize, processSuccessRate, processfailureRate, commErrorRate,
+ tunnelTestResponseTimeSlow
+ - Reduced number of Rates in these RateStats:
+ sendSuccessSize, receiveSize, rejectRate, failRate
+ - ~5KB/profile savings total
+ - Deflate speed calculation once an hour instead of once a day,
+ to improve fast tier selection
+ - Remove dup comment in persisted files
+ * StatisticsManager - effective in 0.7.2:
+ - Spoof uptime to 90m for all
+ - Change tunnel stats from 10m to 60m
+ * Transport:
+ - Maintain a router hash -> IP map in transport,
+ to support additional IP checks (unused for now)
+ - Catch error on pre-2.6 kernels
+ - Some concurrent conversion
+ - Fix an HTML error on peers.jsp
+
+2009-04-01 zzz
+ * I2PTunnel: Fix tunnel close
+ http://forum.i2p/viewtopic.php?t=3231
+
+2009-03-30 zzz
+ * I2CP:
+ - Implement BandwidthLimitsMessage
+ - Have i2psnark use new message, remove
+ build dependency on router
+ * Peer Selection:
+ - Limit peers to a max % of all tunnels with
+ router.maxTunnelPercentage=nn, default 33
+ - Add chart to tunnels.jsp to see results
+
+* 2009-03-29 0.7.1 released
+
+2009-03-29 Complication
+ * Update versions, package release
+
+2009-03-27 zzz
+ * Add readme_fr.html
+ * License splash update
+ * Catch rare TunnelGatewayMessage AIOOB, root cause unknown
+
+2009-03-24 zzz
+ * I2PTunnel:
+ - Add some warnings about new features
+ - Fix encrypted leasesets broken in about -4
+ - Suppress log error on manual stop
+ - Fix NPE on close of a tunnel not open yet
+ * Transport:
+ - Increase default bw to 64/32, burst 80/40
+ * Tunnels: Change some fragmentation errors to warns
+
+2009-03-16 zzz
+ * help.jsp: Add some
+ * I2PTunnel: Cleanup
+ * I2PTunnelHTTPClient: Fix NPE on delayed open
+ * I2PTunnelHTTPServer: Maybe catch an NPE
+ * SOCKS: Allow .onion addresses for onioncat testing
+ * Tunnel: Catch a rare AIOOB
+
+2009-03-09 zzz
+ * Client:
+ - Clean up retry code
+ - Bring I2CP listen error to the summary bar
+ http://forum.i2p/viewtopic.php?t=3133
+ * I2PSnark: Remove the http from the add torrent box
+ * I2PTunnel:
+ - Add persistent key option for standard and IRC clients
+ - Add delay-open option for clients
+ - Get regenerate-dest-on-reconnect working
+ - Add default key file name
+ - Add link to addressbook
+ - I2PSink: Send protocol byte
+ * OCMOSJ:
+ - Change from 5% reply requests to at least
+ once per minute, in hopes of reducing IRC drops
+ - More clean up of the cache cleaning
+ * Routerconsole: Don't OOM configpeer.jsp on huge blocklists
+
+2009-02-26 zzz
+ * I2CP Client: Add support for muxing
+ * I2PTunnel:
+ - Add new IRCServer tunnel type
+ - Add SOCKS 4/4a support
+ - Catch OOMs in HTTPServer
+ - Name the IRCClient filter threads
+ - Port Streamr to I2PTunnel
+ - The beginnings of SOCKS UDP support
+ * Naming: Add reverse lookup by hash
+ * OCMOSJ: Clean up the cache cleaning
+ * Router: Move addShutdownTask from Router to I2PAppContext
+ so that apps can register more easily
+ * Routerconsole:
+ - Thread hard shutdown and restart requests from the routerconsole,
+ and add a delay even if no tunnels, to allow time for a UI response
+ - Sort the summary bar destinations
+ - Move dest-to-hash converter to new helper class so we can
+ use it in i2ptunnel
+
+2009-02-22 sponge
+ * BOB: Orphan tunnel issue fix, bump BOB version
+ * bump to Build 6
+
2009-02-16 zzz
* Streaming lib: Plug timer leak, don't send keepalives
after close, don't disconnect hard after close
diff --git a/initialNews.xml b/initialNews.xml
index 6a9c4b4ca2..0306dcd7b2 100644
--- a/initialNews.xml
+++ b/initialNews.xml
@@ -1,5 +1,5 @@
-
-
+