Compare commits
239 Commits
Author | SHA1 | Date | |
---|---|---|---|
4a9543be78 | |||
fe0d0d6737 | |||
0343e8ffcd | |||
6a6cd14398 | |||
29df534161 | |||
2695461bd4 | |||
186f2bc22f | |||
41718b47c1 | |||
bb51bf49b0 | |||
6c365bef85 | |||
e9063a22d5 | |||
47edc3c853 | |||
09d700e1d6 | |||
e5f19c98a8 | |||
0da964e47f | |||
98fda81b79 | |||
d0a969ca33 | |||
91b3889cbc | |||
33f4fac48f | |||
f70adf8da6 | |||
66eae60c48 | |||
cf02abd19c | |||
ca3b6eb00d | |||
ae2f48f55d | |||
f2bfa2e15c | |||
ee0aada892 | |||
7179a64fee | |||
f3ddf3fa93 | |||
91b8f7c2ae | |||
54f1c0ec66 | |||
1d690f46ae | |||
ca783caff1 | |||
c4fa0d894f | |||
03f16565fe | |||
5785f500ef | |||
8f5257d5dc | |||
c455fa6309 | |||
59b624a4a4 | |||
bfa02f3b82 | |||
60ab94689c | |||
7f33eb4959 | |||
467095f85e | |||
1fc890c6f0 | |||
3733b78ccf | |||
6648e182ae | |||
56473c6b65 | |||
d222c7a998 | |||
84bd8274ad | |||
0d2812db50 | |||
6484005569 | |||
559653f0ab | |||
7a684c160b | |||
7e21afe6a6 | |||
720aa704c4 | |||
532077a4c1 | |||
8bce2fd7a2 | |||
3603cc23ee | |||
f4c3607c4d | |||
fbe7e42f46 | |||
f3143d8b3d | |||
fd32d77976 | |||
39e8e93bfa | |||
e151ef74e1 | |||
609e70692d | |||
c5ac0981b5 | |||
129fc5b838 | |||
775ab9a7bf | |||
374360c7b4 | |||
cc3165bf72 | |||
6b0a2464dd | |||
7b12f700dd | |||
806e2f88c8 | |||
8591dfe71c | |||
7756e20b86 | |||
39a1958bf4 | |||
f9d8a2d79b | |||
cdab99bd25 | |||
f344c9e0be | |||
7acaa964af | |||
08deabb262 | |||
6504e1f91d | |||
b125276be9 | |||
dc9607024e | |||
06e1305df2 | |||
28a14782a6 | |||
e7bccb2f47 | |||
bdf7dda3b4 | |||
a7d4b3d6ba | |||
a82de3d1cf | |||
a6dc27adaf | |||
69f051da41 | |||
5946c35a88 | |||
3d8cb3b90d | |||
3b9fec1857 | |||
ececf5407d | |||
d236b9b44a | |||
7ec29b0c5a | |||
8d7340500f | |||
1ee2b5e899 | |||
6f948df089 | |||
b6b1491368 | |||
f70be29651 | |||
c48700216c | |||
45a2159290 | |||
ac7ea4ac4c | |||
2a96dde20b | |||
78d5080d78 | |||
395baf0274 | |||
951f082884 | |||
a5ab6f576d | |||
f7f93fda0c | |||
7365ca849f | |||
d75e1deae7 | |||
4aa9c7fdcf | |||
69e6393442 | |||
9d9d4093bc | |||
37f9d3afe1 | |||
82180592f9 | |||
d88cfae80d | |||
6235b49300 | |||
4682bb4147 | |||
baebd1fdd2 | |||
6ed17c1a5f | |||
ae0bcc492d | |||
d8298c63ab | |||
9a089b7da0 | |||
e5d76a5a77 | |||
f7170aa00a | |||
c02711ccad | |||
9885779cab | |||
e105ca92f2 | |||
28cfd8cffe | |||
a4468219c7 | |||
70f07e5bc7 | |||
173e8a0434 | |||
10e2c3832d | |||
c620420a6f | |||
6be54942ec | |||
ab92206b77 | |||
0e2a4227ef | |||
8d891b99d1 | |||
72fd42ef9b | |||
ba7dbf9064 | |||
807f0665b1 | |||
416b0e4540 | |||
011ded2ee4 | |||
f9faf3c70d | |||
0ea532c72e | |||
104cf8346e | |||
3e7e5d6113 | |||
0275c5e13b | |||
1c76d240e0 | |||
366da1b37c | |||
bdcb625e6d | |||
8296723533 | |||
957c809774 | |||
70b99cf4f9 | |||
05a6353142 | |||
85615b972b | |||
e3abea1ad2 | |||
bc54908a22 | |||
60bd9803f0 | |||
c3360cc3d7 | |||
aa71725159 | |||
574713e608 | |||
0aaae0b0da | |||
ed34964747 | |||
7b758d89d0 | |||
1c7111eca0 | |||
831f09c91a | |||
4f836a20e1 | |||
8faeaaa1ae | |||
7271289c1f | |||
8421ae1ed4 | |||
d042c6b921 | |||
e2e4516a8f | |||
efc604a25c | |||
5c1864ed5e | |||
debf92fd9b | |||
9477b139be | |||
53ce3c4802 | |||
d61af12867 | |||
908c542b40 | |||
ef998349cc | |||
44446d76e4 | |||
a616a5f1c9 | |||
c0b616e519 | |||
b4d3986006 | |||
ba9108f937 | |||
161379f004 | |||
841feaedff | |||
ba8de6c565 | |||
d6148db455 | |||
33b43f40b9 | |||
4336dc441e | |||
2d86e7cf60 | |||
219e96d416 | |||
0c72fe7383 | |||
369599fedd | |||
847c9dafce | |||
734818f651 | |||
dae6fd47d9 | |||
0956393cf3 | |||
d16f187394 | |||
962a8f6f49 | |||
9aa8707647 | |||
1fdd228a9d | |||
819d857550 | |||
04fb12932f | |||
703b6ed190 | |||
bd6c63cc7e | |||
7dbb13d6dc | |||
ebdc69cbc2 | |||
868fe90d7a | |||
9e39f34473 | |||
45ed744210 | |||
701904d119 | |||
dcf4bb595f | |||
e9f27c60dd | |||
321f11c055 | |||
85cebc7992 | |||
8e5c4a3e22 | |||
dff75de97a | |||
f1fd35265a | |||
b73b3fc5ac | |||
13d4ccf2e7 | |||
8c9ac941bf | |||
3fc698c7d3 | |||
15596c9230 | |||
7fdbe9b87b | |||
5acc56c184 | |||
c524231c6d | |||
5d4a7967cb | |||
01101f9867 | |||
bad4c4a133 | |||
0ff8167425 | |||
134764b154 | |||
23699e46e5 | |||
fa23a7b066 |
183
LICENSE.txt
Normal file
183
LICENSE.txt
Normal file
@ -0,0 +1,183 @@
|
||||
This product includes both public domain code and licensed code as described below.
|
||||
For all code, unless otherwise stated in the appropriate license, the following applies:
|
||||
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
|
||||
|
||||
LICENSES
|
||||
--------
|
||||
|
||||
Core:
|
||||
Public domain except as listed below:
|
||||
|
||||
ElGamal and DSA code:
|
||||
Copyright (c) 2003, TheCrypto
|
||||
See licenses/LICENSE-ElGamalDSA.txt
|
||||
|
||||
SHA256 and HMAC-SHA256:
|
||||
Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
|
||||
See licenses/LICENSE-SHA256.txt
|
||||
|
||||
AES code:
|
||||
Under the Cryptix (MIT) license, written by the Cryptix team
|
||||
(That's what our website says but all our AES code looks like it is public domain)
|
||||
|
||||
Crypto filters:
|
||||
From the xlattice app - http://xlattice.sourceforge.net/
|
||||
See licenses/LICENSE-BSD.txt
|
||||
|
||||
SNTP code:
|
||||
Copyright (c) 2004, Adam Buckley
|
||||
See licenses/LICENSE-SNTP.txt
|
||||
|
||||
PRNG:
|
||||
Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
GMP 4.1.3:
|
||||
Copyright 1991, 1996, 1999, 2000 Free Software Foundation, Inc.
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
HashCash code:
|
||||
Copyright 2006 Gregory Rubin grrubin@gmail.com
|
||||
See licenses/LICENSE-HashCash.txt
|
||||
|
||||
|
||||
|
||||
Router:
|
||||
Public domain
|
||||
|
||||
|
||||
|
||||
Installer:
|
||||
Launch4j:
|
||||
Copyright (C) 2005 Grzegorz Kowal
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Izpack:
|
||||
See licenses/LICENSE-Apache1.1.txt
|
||||
|
||||
|
||||
|
||||
Wrapper:
|
||||
Copyright (c) 1999, 2004 Tanuki Software
|
||||
See licenses/LICENSE-Wrapper.txt
|
||||
|
||||
|
||||
|
||||
Applications:
|
||||
|
||||
Addressbook:
|
||||
Copyright (c) 2004 Ragnarok
|
||||
See licenses/LICENSE-Addressbook.txt
|
||||
|
||||
BOB:
|
||||
Copyright (C) sponge
|
||||
DWTFYWTPL
|
||||
|
||||
I2PSnark:
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
I2PTunnel:
|
||||
(c) 2003 - 2004 mihi
|
||||
GPLv2 with exception.
|
||||
See licenses/LICENSE-I2PTunnel.txt
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
I2PTunnel SOCKS Proxy:
|
||||
Copyright (c) 2004 by human
|
||||
GPLv2 with exception.
|
||||
See licenses/LICENSE-I2PTunnel.txt
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
I2PTunnel UDP and Streamr:
|
||||
By welterde.
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Jetty 5.1.12:
|
||||
Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
|
||||
See licenses/LICENSE-Apache1.1.txt
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/NOTICE-Ant.txt
|
||||
See licenses/NOTICE-Commons-Logging.txt
|
||||
|
||||
JRobin 1.4.0:
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
Ministreaming Lib:
|
||||
By mihi.
|
||||
See licenses/LICENSE-BSD.txt
|
||||
|
||||
Proxyscript:
|
||||
By Cervantes.
|
||||
Public domain.
|
||||
|
||||
Router console:
|
||||
Public domain.
|
||||
|
||||
SAM:
|
||||
Public domain.
|
||||
|
||||
Streaming Lib:
|
||||
Public domain.
|
||||
|
||||
SusiDNS:
|
||||
Copyright (C) 2005 <susi23@mail.i2p>
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
SusiMail:
|
||||
Copyright (C) 2004-2005 <susi23@mail.i2p>
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Systray:
|
||||
Public domain.
|
||||
Bundles systray4j code:
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
|
||||
|
||||
Other Applications and Libraries
|
||||
--------------------------------
|
||||
The following applications and libraries are not used or bundled in
|
||||
binary packages, therefore the licenses are not included in binary
|
||||
distributions. See the source package for the additional license information.
|
||||
|
||||
Atalk:
|
||||
Public domain
|
||||
|
||||
SAM C Library:
|
||||
Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>
|
||||
See apps/sam/c/doc/license.txt
|
||||
|
||||
SAM C# Library:
|
||||
Public domain.
|
||||
See apps/sam/csharp/README
|
||||
|
||||
SAM Perl Library:
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
SAM Python Library:
|
||||
Public domain.
|
29
README.txt
Normal file
29
README.txt
Normal file
@ -0,0 +1,29 @@
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
|
||||
Apache Ant 1.7.0 or higher
|
||||
|
||||
To build:
|
||||
ant pkg
|
||||
Run 'ant' with no arguments to see other build options.
|
||||
See http://www.i2p2.de/download.html for installation instructions.
|
||||
|
||||
Documentation:
|
||||
http://www.i2p2.de/
|
||||
API: run 'ant javadoc' then start at build/javadoc/index.html
|
||||
|
||||
Latest release:
|
||||
http://www.i2p2.de/download.html
|
||||
|
||||
To get development branch from source control:
|
||||
http://www.i2p2.de/newdevelopers.html
|
||||
|
||||
FAQ:
|
||||
http://www.i2p2.de/faq.html
|
||||
|
||||
Need help?
|
||||
IRC irc.freenode.net #i2p
|
||||
http://forum.i2p2.de/
|
||||
|
||||
Licenses:
|
||||
See LICENSE.txt
|
||||
|
30
Slackware/README
Normal file
30
Slackware/README
Normal file
@ -0,0 +1,30 @@
|
||||
ou will need atleast monotone > = 0.41 to get the most recent build source
|
||||
and connect it to an already running i2p router.
|
||||
|
||||
OR:
|
||||
|
||||
You may download the actual "stable" source from
|
||||
http://code.google.com/p/i2p/downloads/list
|
||||
|
||||
You will need to follwing tools to build the i2p and i2p-base packages:
|
||||
|
||||
bash >= 3.1.017
|
||||
requiredbuilder >= 0.16.3 ( http://www.stabellini.net/requiredbuilder.html )
|
||||
jre >= 6u11
|
||||
jdk >= 6u11
|
||||
apache-ant >= 1.7.1
|
||||
perl >= 5.10.0
|
||||
python >= 2.5.2
|
||||
|
||||
Reccomended:
|
||||
monotone >= 0.41 ( http://pkgs.dr.ea.ms )
|
||||
|
||||
See also:
|
||||
|
||||
i2p/readme.txt
|
||||
|
||||
AND
|
||||
|
||||
i2p-base/readme.txt
|
||||
|
||||
for information and handy tips.
|
8
Slackware/i2p-base/build.xml
Normal file
8
Slackware/i2p-base/build.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project basedir="." default="slackpkg">
|
||||
<target name="slackpkg">
|
||||
<echo message="Building Slackware package." />
|
||||
<exec executable="./i2p-base.SlackBuild">
|
||||
</exec>
|
||||
</target>
|
||||
</project>
|
45
Slackware/i2p-base/doinst.sh
Normal file
45
Slackware/i2p-base/doinst.sh
Normal file
@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
touch /etc/rc.d/rc.local
|
||||
touch /etc/rc.d/rc.local_shutdown
|
||||
|
||||
I2PRCA=`grep -c /etc/rc.d/rc.local -e i2p`
|
||||
I2PRCB=`grep -c /etc/rc.d/rc.local_shutdown -e i2p`
|
||||
|
||||
echo
|
||||
|
||||
if [ $I2PRCA -eq 0 ] ; then
|
||||
echo "if [ -x /etc/rc.d/rc.i2p ] ; then" >> /etc/rc.d/rc.local
|
||||
echo " sh /etc/rc.d/rc.i2p start" >> /etc/rc.d/rc.local
|
||||
echo "fi" >> /etc/rc.d/rc.local
|
||||
echo "/etc/rc.d/rc.local modified."
|
||||
else
|
||||
echo "/etc/rc.d/rc.local looks OK"
|
||||
fi
|
||||
|
||||
if [ $I2PRCB -eq 0 ] ; then
|
||||
echo "if [ -x /etc/rc.d/rc.i2p ] ; then" >> /etc/rc.d/rc.local_shutdown
|
||||
echo " sh /etc/rc.d/rc.i2p stop" >> /etc/rc.d/rc.local_shutdown
|
||||
echo "fi" >> /etc/rc.d/rc.local_shutdown
|
||||
echo "/etc/rc.d/rc.local_shutdown modified."
|
||||
else
|
||||
echo "/etc/rc.d/rc.local_shutdown looks OK"
|
||||
fi
|
||||
|
||||
if [ -f /etc/rc.d/rc.i2p ] ; then
|
||||
if [ -x /etc/rc.d/rc.i2p ] ; then
|
||||
chmod +x /etc/rc.d/rc.i2p.new
|
||||
fi
|
||||
echo
|
||||
echo "It apears that you already have /etc/rc.d/rc.i2p"
|
||||
echo "You may wish to replace it with /etc/rc.d/rc.i2p.new"
|
||||
echo
|
||||
else
|
||||
mv /etc/rc.d/rc.i2p.new /etc/rc.d/rc.i2p
|
||||
echo
|
||||
echo "Installation finished. The i2p start/stop script has been"
|
||||
echo "installed on /etc/rc.d directory. You should chmod +x"
|
||||
echo '/etc/rc.d/rc.i2p to start it on boot.'
|
||||
echo
|
||||
fi
|
||||
|
||||
exit
|
42
Slackware/i2p-base/i2p-base.SlackBuild
Normal file
42
Slackware/i2p-base/i2p-base.SlackBuild
Normal file
@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
# Heavily based on the Slackware 12.1 SlackBuild
|
||||
# Slackware build script for i2p
|
||||
|
||||
# PLEASE READ THIS:
|
||||
# Probably you will never have to update i2p packages with upgradepkg,
|
||||
# just because i2p have an auto-update function.
|
||||
# How to start i2p:
|
||||
# After installpkg command, doinst.sh will execute a postinstallation script
|
||||
# needed by i2p. After that you have to chmod +x /etc/rc.d/rc.i2p and start
|
||||
# i2p service with /etc/rc.d/rc.i2p start.
|
||||
# Now tell your browser to user this proxy: localhost on port 4444 and open
|
||||
# this page: http://localhost:7657/index.jsp
|
||||
# Here you can configure i2p, watch network status and navigate anonimously.
|
||||
# It's suggested to subscribe to various dns host, like i2host.i2p
|
||||
# For any additional information, visit i2host.i2p and forum.i2p
|
||||
|
||||
CWD=$(pwd)
|
||||
TMP=/tmp
|
||||
PKG=/$TMP/package-base-i2p
|
||||
rm -rf $PKG
|
||||
mkdir -p $PKG
|
||||
# put here installation dir, without first and last /
|
||||
# es: usr/local
|
||||
NAME=i2p-base
|
||||
VERSION=0.0.1
|
||||
BUILD=1sim
|
||||
ARCH=noarch
|
||||
INSTALL_DIR=opt
|
||||
cd $PKG
|
||||
chown -R root:root .
|
||||
|
||||
mkdir -p $PKG/etc/rc.d
|
||||
mkdir -p $PKG/install
|
||||
sed "s|directory|/$INSTALL_DIR/i2p/i2prouter|g" $CWD/rc.i2p_def > $PKG/etc/rc.d/rc.i2p.new
|
||||
chmod 644 $PKG/etc/rc.d/rc.i2p.new
|
||||
sed "s|directory|/$INSTALL_DIR/i2p/|g" $CWD/doinst.sh > $PKG/install/doinst.sh
|
||||
cat $CWD/slack-desc > $PKG/install/slack-desc
|
||||
|
||||
cd $PKG
|
||||
requiredbuilder -v -y -s $CWD $PKG
|
||||
makepkg -l y -c n $CWD/${NAME}-$VERSION-$ARCH-$BUILD.tgz
|
27
Slackware/i2p-base/rc.i2p_def
Normal file
27
Slackware/i2p-base/rc.i2p_def
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Start/stop i2p service.
|
||||
|
||||
i2p_start() {
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory start )"
|
||||
}
|
||||
|
||||
i2p_stop() {
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory stop )"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
'start')
|
||||
i2p_start
|
||||
;;
|
||||
'stop')
|
||||
i2p_stop
|
||||
;;
|
||||
'restart')
|
||||
i2p_stop
|
||||
i2p_start
|
||||
;;
|
||||
*)
|
||||
echo "usage $0 start|stop|restart"
|
||||
;;
|
||||
esac
|
||||
|
10
Slackware/i2p-base/readme.txt
Normal file
10
Slackware/i2p-base/readme.txt
Normal file
@ -0,0 +1,10 @@
|
||||
An rc file called rc.i2p has been placed into the /etc/rc.d directory.
|
||||
If you want to change installation dir, change the variable INSTALL_DIR
|
||||
on base-i2p.SlackBuild and rebuild the package. You also will need to do the
|
||||
same for the i2p package.
|
||||
|
||||
The install script will insert everything needed into /etc/rc.d/rc.local and
|
||||
into /etc/rc.d/rc.local_shutdown automatically.
|
||||
|
||||
If you want to start I2P at boot you have to chmod +x /etc/rc.d/rc.i2p
|
||||
|
19
Slackware/i2p-base/slack-desc
Normal file
19
Slackware/i2p-base/slack-desc
Normal file
@ -0,0 +1,19 @@
|
||||
# HOW TO EDIT THIS FILE:
|
||||
# The "handy ruler" below makes it easier to edit a package description. Line
|
||||
# up the first '|' above the ':' following the base package name, and the '|' on
|
||||
# the right side marks the last column you can put a character in. You must make
|
||||
# exactly 11 lines for the formatting to be correct. It's also customary to
|
||||
# leave one space after the ':'.
|
||||
|
||||
|-----handy-ruler------------------------------------------------------|
|
||||
base-i2p: base-i2p (I2P anonymizing network base config files)
|
||||
base-i2p:
|
||||
base-i2p: I2P is an anonymizing network, offering a simple layer that
|
||||
base-i2p: identity-sensitive applications can use to securely communicate. All
|
||||
base-i2p: data is wrapped with several layers of encryption, and the network is
|
||||
base-i2p: both distributed and dynamic, with no trusted parties.
|
||||
base-i2p: Many applications are available that interface with I2P, including
|
||||
base-i2p: mail, peer-peer file sharing, IRC chat, and others.
|
||||
base-i2p:
|
||||
base-i2p: This package provides the startup files.
|
||||
base-i2p:
|
1
Slackware/i2p-base/slack-required
Normal file
1
Slackware/i2p-base/slack-required
Normal file
@ -0,0 +1 @@
|
||||
bash >= 3.1.017
|
8
Slackware/i2p/build.xml
Normal file
8
Slackware/i2p/build.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project basedir="." default="slackpkg">
|
||||
<target name="slackpkg">
|
||||
<echo message="Building Slackware package." />
|
||||
<exec executable="./i2p.SlackBuild">
|
||||
</exec>
|
||||
</target>
|
||||
</project>
|
67
Slackware/i2p/doinst.sh
Normal file
67
Slackware/i2p/doinst.sh
Normal file
@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
|
||||
INST_DIR=directory
|
||||
|
||||
( cd install
|
||||
|
||||
echo
|
||||
for i in *.config ; {
|
||||
if [ -f $INST_DIR/$i ] ; then
|
||||
echo "Please check ${INST_DIR}${i}, as there is a new version."
|
||||
cp $i $INST_DIR/$i.new
|
||||
else
|
||||
cp $i $INST_DIR/$i
|
||||
fi
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
( cd $INST_DIR
|
||||
if [ -f blocklist.txt ] ; then
|
||||
echo "Please check ${INST_DIR}blocklist.txt, as there is a new version."
|
||||
else
|
||||
mv blocklist.txt.new blocklist.txt
|
||||
fi
|
||||
)
|
||||
|
||||
( cd $INST_DIR/eepsite
|
||||
if [ -f jetty.xml ] ; then
|
||||
rm jetty.xml.new
|
||||
else
|
||||
mv jetty.xml.new jetty.xml
|
||||
fi
|
||||
)
|
||||
|
||||
( cd $INST_DIR/eepsite/docroot
|
||||
if [ -f index.html ] ; then
|
||||
rm index.html.new
|
||||
else
|
||||
mv index.html.new index.html
|
||||
fi
|
||||
if [ -f favicon.ico ] ; then
|
||||
rm favicon.ico.new
|
||||
else
|
||||
mv favicon.ico.new favicon.ico
|
||||
fi
|
||||
)
|
||||
|
||||
echo
|
||||
echo "FINISHING I2P INSTALLATION. PLEASE WAIT."
|
||||
|
||||
cd $INST_DIR
|
||||
sh postinstall.sh || (
|
||||
echo "ERROR: failed execution of postinstall.sh. Please"
|
||||
echo "cd into i2p installation directory and run "
|
||||
echo "postinstall.sh manually with ./postinstall.sh"
|
||||
exit 1
|
||||
)
|
||||
|
||||
sleep 10
|
||||
|
||||
sh i2prouter stop || exit 1
|
||||
|
||||
echo
|
||||
echo "Installation finished."
|
||||
echo
|
||||
|
||||
exit
|
88
Slackware/i2p/i2p.SlackBuild
Executable file
88
Slackware/i2p/i2p.SlackBuild
Executable file
@ -0,0 +1,88 @@
|
||||
#!/bin/sh
|
||||
# Heavily based on the Slackware 12.1 SlackBuild
|
||||
# Slackware build script for i2p
|
||||
|
||||
# PLEASE READ THIS:
|
||||
# Probably you will never have to update i2p packages with upgradepkg,
|
||||
# just because i2p have an auto-update function.
|
||||
# How to start i2p:
|
||||
# After installpkg command, doinst.sh will execute a postinstallation script
|
||||
# needed by i2p. After that you have to chmod +x /etc/rc.d/rc.i2p and start
|
||||
# i2p service with /etc/rc.d/rc.i2p start.
|
||||
# Now tell your browser to user this proxy: localhost on port 4444 and open
|
||||
# this page: http://localhost:7657/index.jsp
|
||||
# Here you can configure i2p, watch network status and navigate anonimously.
|
||||
# It's suggested to subscribe to various dns host, like i2host.i2p
|
||||
# For any additional information, visit i2host.i2p and forum.i2p
|
||||
|
||||
BUILD=1sim
|
||||
|
||||
# put here installation dir, without first and last /
|
||||
# es: usr/local
|
||||
INSTALL_DIR=opt
|
||||
NAME=i2p
|
||||
ARCH=noarch
|
||||
|
||||
|
||||
#
|
||||
# This mess is here due to the totally moronic way i2p does versioning.
|
||||
# We correct it here.
|
||||
#
|
||||
ROUTER=$(echo -ne "_")$(cat ../../router/java/src/net/i2p/router/RouterVersion.java | grep -e "public final static long BUILD" | cut -f2 -d"=" | cut -f1 -d";" | sed -re "s/ //g")
|
||||
if [ "$ROUTER" == "_" ] ; then
|
||||
ROUTER="_0"
|
||||
fi
|
||||
|
||||
#
|
||||
# That was the easy one, now for the tough one.
|
||||
#
|
||||
|
||||
CORE=$(cat ../../core/java/src/net/i2p/CoreVersion.java | grep -e "public final static String VERSION" | cut -f2 -d'"' | sed -re "s/ //g")
|
||||
CORE1=$(echo -n $CORE.x.x | sed -re "s/(.*)\.(.*)\.(.*)\.(.*)/\1/")
|
||||
CORE2=$(echo -n $CORE.x | sed -re "s/(.*)\.(.*)\.(.*)\.(.*)/\1/")
|
||||
|
||||
if [ "$CORE.x.x" == "$CORE1" ] ; then
|
||||
CORE=$(echo -ne $CORE".0.0")
|
||||
fi
|
||||
if [ "$CORE.x" == "$CORE2" ] ; then
|
||||
CORE=$(echo -ne $CORE".0")
|
||||
fi
|
||||
|
||||
VERSION=$(echo $CORE$ROUTER)
|
||||
#
|
||||
# Whew!
|
||||
# OK, let's build i2p
|
||||
#
|
||||
|
||||
CWD=$(pwd)
|
||||
TMP=/tmp
|
||||
|
||||
PKG=$TMP/package-i2p
|
||||
rm -rf $PKG
|
||||
mkdir -p $PKG
|
||||
|
||||
cd $CWD/../../
|
||||
|
||||
ant distclean
|
||||
ant dist
|
||||
|
||||
|
||||
tar xjvf i2p.tar.bz2 -C $TMP
|
||||
|
||||
cd $TMP/i2p
|
||||
chown -R root:root .
|
||||
|
||||
mkdir -p $PKG/$INSTALL_DIR/
|
||||
cp -a ../i2p $PKG/$INSTALL_DIR/
|
||||
|
||||
mkdir -p $PKG/install
|
||||
mv $PKG/$INSTALL_DIR/i2p/*.config $PKG/install
|
||||
mv $PKG/$INSTALL_DIR/i2p/blocklist.txt $PKG/$INSTALL_DIR/i2p/blocklist.txt.new
|
||||
mv $PKG/$INSTALL_DIR/i2p/eepsite/jetty.xml $PKG/$INSTALL_DIR/i2p/eepsite/jetty.xml.new
|
||||
mv $PKG/$INSTALL_DIR/i2p/eepsite/docroot/index.html $PKG/$INSTALL_DIR/i2p/eepsite/docroot/index.html.new
|
||||
mv $PKG/$INSTALL_DIR/i2p/eepsite/docroot/favicon.ico $PKG/$INSTALL_DIR/i2p/eepsite/docroot/favicon.ico.new
|
||||
sed "s|directory|/$INSTALL_DIR/i2p/|g" $CWD/doinst.sh > $PKG/install/doinst.sh
|
||||
cat $CWD/slack-desc > $PKG/install/slack-desc
|
||||
cd $PKG
|
||||
requiredbuilder -v -y -s $CWD $PKG
|
||||
makepkg -l y -c n $CWD/${NAME}-$VERSION-$ARCH-$BUILD.tgz
|
47
Slackware/i2p/readme.txt
Normal file
47
Slackware/i2p/readme.txt
Normal file
@ -0,0 +1,47 @@
|
||||
Building:
|
||||
The i2p package will be installed in /opt/i2p
|
||||
|
||||
If you want to change installation dir, change the variable INSTALL_DIR
|
||||
on i2p.SlackBuild and rebuild the package. You will also need to do the same
|
||||
in the base-i2p package.
|
||||
|
||||
Installation and Upgrade:
|
||||
Probably you will never have to update i2p packages. However if you do,
|
||||
be sure to installpkg first, then removepkg or custom config files can
|
||||
be lost with upgradepkg. I2P has an auto-update function. However using
|
||||
installpkg then removepkg lowers the demand on the I2P network as a
|
||||
whole, and is by far faster.
|
||||
|
||||
After installpkg command, doinst.sh will execute a postinstallation script
|
||||
needed by I2P. Be sure to also install the base-i2p package.
|
||||
|
||||
Optional:
|
||||
|
||||
chmod +x /etc/rc.d/rc.i2p only if you want it to start on boot and stop on
|
||||
shutdown.
|
||||
|
||||
How to start I2P:
|
||||
|
||||
Start I2P service with-
|
||||
sh /etc/rc.d/rc.i2p start
|
||||
|
||||
Now tell your browser to user this proxy: localhost on port 4444 and open
|
||||
this page: http://localhost:7657/index.jsp
|
||||
Here you can configure I2P, watch network status and navigate anonimously.
|
||||
It's suggested to subscribe to various addressbook hosts so that you can
|
||||
get to the many available eepsites and other service on I2P. These are not
|
||||
set up by default for security reasons.
|
||||
|
||||
Please see the faqs on http://www.i2p2.i2p/ or http://www.i2p2.de/ on how
|
||||
to subscribe to the various addressbook services.
|
||||
|
||||
To stop I2P:
|
||||
/etc/rc.d/rc.i2p stop
|
||||
|
||||
|
||||
For any additional information:
|
||||
|
||||
Within I2P- http://www.i2p2.i2p/, http://forum.i2p/, http://zzz.i2p
|
||||
|
||||
Internet (not reccomended!) - http://www.i2p2.de/, http://forum.i2p2.de/
|
||||
|
19
Slackware/i2p/slack-desc
Normal file
19
Slackware/i2p/slack-desc
Normal file
@ -0,0 +1,19 @@
|
||||
# HOW TO EDIT THIS FILE:
|
||||
# The "handy ruler" below makes it easier to edit a package description. Line
|
||||
# up the first '|' above the ':' following the base package name, and the '|' on
|
||||
# the right side marks the last column you can put a character in. You must make
|
||||
# exactly 11 lines for the formatting to be correct. It's also customary to
|
||||
# leave one space after the ':'.
|
||||
|
||||
|-----handy-ruler----------------------------------------------------------|
|
||||
i2p: i2p (an anonymizing network)
|
||||
i2p:
|
||||
i2p: I2P is an anonymizing network, offering a simple layer that
|
||||
i2p: identity-sensitive applications can use to securely communicate. All
|
||||
i2p: data is wrapped with several layers of encryption, and the network is
|
||||
i2p: both distributed and dynamic, with no trusted parties.
|
||||
i2p: Many applications are available that interface with I2P, including
|
||||
i2p: mail, peer-peer file sharing, IRC chat, and others.
|
||||
i2p: WARNING: To upgrade installpkg FIRST _THEN_ removepkg.
|
||||
i2p: For more information, see: http://www.i2p2.de/
|
||||
i2p:
|
2
Slackware/i2p/slack-required
Normal file
2
Slackware/i2p/slack-required
Normal file
@ -0,0 +1,2 @@
|
||||
glibc >= 2.7-i486-17 | glibc-solibs >= 2.7-i486-17
|
||||
perl >= 5.10.0-i486-1
|
14
apps/BOB/bob.config
Normal file
14
apps/BOB/bob.config
Normal file
@ -0,0 +1,14 @@
|
||||
#bob.config
|
||||
#Tue Dec 30 00:00:00 UTC 2008
|
||||
# Please leave this file here for testing.
|
||||
# Thank you,
|
||||
# Sponge
|
||||
i2cp.tcp.port=7654
|
||||
BOB.host=localhost
|
||||
inbound.lengthVariance=0
|
||||
i2cp.messageReliability=BestEffort
|
||||
BOB.port=45067
|
||||
outbound.length=1
|
||||
inbound.length=1
|
||||
outbound.lengthVariance=0
|
||||
i2cp.tcp.host=localhost
|
@ -1,3 +1,4 @@
|
||||
compile.on.save=false
|
||||
do.depend=false
|
||||
do.jar=true
|
||||
javac.debug=true
|
||||
|
@ -1,5 +1,11 @@
|
||||
application.title=BOB
|
||||
application.vendor=root
|
||||
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=false
|
||||
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=8
|
||||
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=8
|
||||
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8
|
||||
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80
|
||||
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project
|
||||
build.classes.dir=${build.dir}/classes
|
||||
build.classes.excludes=**/*.java,**/*.form
|
||||
# This directory is removed when the project is cleaned:
|
||||
@ -30,13 +36,31 @@ file.reference.mstreaming.jar-1=../ministreaming/java/build/mstreaming.jar
|
||||
file.reference.NetBeansProjects-i2p.i2p=../i2p.i2p/
|
||||
file.reference.streaming.jar=../../bob/i2p/i2p.i2p/build/streaming.jar
|
||||
file.reference.streaming.jar-1=../streaming/java/build/streaming.jar
|
||||
file.reference.wrapper-freebsd=../../installer/lib/wrapper/freebsd/
|
||||
file.reference.wrapper-linux=../../installer/lib/wrapper/linux/
|
||||
file.reference.wrapper-linux64=../../installer/lib/wrapper/linux64/
|
||||
file.reference.wrapper-macosx=../../installer/lib/wrapper/macosx/
|
||||
file.reference.wrapper-solaris=../../installer/lib/wrapper/solaris/
|
||||
file.reference.wrapper-win32=../../installer/lib/wrapper/win32/
|
||||
file.reference.wrapper.jar=../../installer/lib/wrapper/linux/wrapper.jar
|
||||
file.reference.wrapper.jar-1=../../installer/lib/wrapper/freebsd/wrapper.jar
|
||||
file.reference.wrapper.jar-2=../../installer/lib/wrapper/linux64/wrapper.jar
|
||||
file.reference.wrapper.jar-3=../../installer/lib/wrapper/macosx/wrapper.jar
|
||||
file.reference.wrapper.jar-4=../../installer/lib/wrapper/solaris/wrapper.jar
|
||||
file.reference.wrapper.jar-5=../../installer/lib/wrapper/win32/wrapper.jar
|
||||
includes=**
|
||||
jar.compress=false
|
||||
javac.classpath=\
|
||||
${file.reference.i2p.jar-1}:\
|
||||
${file.reference.i2ptunnel.jar}:\
|
||||
${file.reference.mstreaming.jar-1}:\
|
||||
${file.reference.streaming.jar-1}
|
||||
${file.reference.streaming.jar-1}:\
|
||||
${file.reference.wrapper.jar-1}:\
|
||||
${file.reference.wrapper.jar}:\
|
||||
${file.reference.wrapper.jar-2}:\
|
||||
${file.reference.wrapper.jar-3}:\
|
||||
${file.reference.wrapper.jar-4}:\
|
||||
${file.reference.wrapper.jar-5}
|
||||
# Space-separated list of extra javac options
|
||||
javac.compilerargs=
|
||||
javac.deprecation=false
|
||||
@ -58,6 +82,12 @@ javadoc.splitindex=true
|
||||
javadoc.use=true
|
||||
javadoc.version=false
|
||||
javadoc.windowtitle=
|
||||
jnlp.codebase.type=local
|
||||
jnlp.codebase.url=file:/root/NetBeansProjects/i2p.i2p/apps/BOB/dist/
|
||||
jnlp.descriptor=application
|
||||
jnlp.enabled=false
|
||||
jnlp.offline-allowed=false
|
||||
jnlp.signed=false
|
||||
main.class=net.i2p.BOB.Main
|
||||
manifest.file=manifest.mf
|
||||
meta.inf.dir=${src.dir}/META-INF
|
||||
|
@ -34,7 +34,6 @@ import java.util.Properties;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.RetransmissionTimer;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
/**
|
||||
* <span style="font-size:8px;font-family:courier;color:#EEEEEE;background-color:#000000">
|
||||
* ################################################################################<br>
|
||||
@ -114,6 +113,18 @@ public class BOB {
|
||||
public final static String PROP_BOB_HOST = "BOB.host";
|
||||
private static int maxConnections = 0;
|
||||
private static NamedDB database;
|
||||
private static Properties props = new Properties();
|
||||
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param arg
|
||||
*/
|
||||
public static void info(String arg) {
|
||||
System.out.println("INFO:" + arg);
|
||||
_log.info(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
@ -121,7 +132,7 @@ public class BOB {
|
||||
* @param arg
|
||||
*/
|
||||
public static void warn(String arg) {
|
||||
System.out.println(arg);
|
||||
System.out.println("WARNING:" + arg);
|
||||
_log.warn(arg);
|
||||
}
|
||||
|
||||
@ -131,7 +142,7 @@ public class BOB {
|
||||
* @param arg
|
||||
*/
|
||||
public static void error(String arg) {
|
||||
System.out.println(arg);
|
||||
System.out.println("ERROR: " + arg);
|
||||
_log.error(arg);
|
||||
}
|
||||
|
||||
@ -147,11 +158,10 @@ public class BOB {
|
||||
// Set up all defaults to be passed forward to other threads.
|
||||
// Re-reading the config file in each thread is pretty damn stupid.
|
||||
// I2PClient client = I2PClientFactory.createClient();
|
||||
Properties props = new Properties();
|
||||
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
|
||||
|
||||
// This is here just to ensure there is no interference with our threadgroups.
|
||||
SimpleTimer Y = RetransmissionTimer.getInstance();
|
||||
RetransmissionTimer Y = RetransmissionTimer.getInstance();
|
||||
i = Y.hashCode();
|
||||
{
|
||||
try {
|
||||
@ -202,12 +212,13 @@ public class BOB {
|
||||
props.store(fo, configLocation);
|
||||
fo.close();
|
||||
} catch(IOException ioe) {
|
||||
warn("IOException on BOB config file " + configLocation + ", " + ioe);
|
||||
error("IOException on BOB config file " + configLocation + ", " + ioe);
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
try {
|
||||
warn("BOB is now running.");
|
||||
info("BOB is now running.");
|
||||
ServerSocket listener = new ServerSocket(Integer.parseInt(props.getProperty(PROP_BOB_PORT)), 10, InetAddress.getByName(props.getProperty(PROP_BOB_HOST)));
|
||||
Socket server;
|
||||
|
||||
@ -220,7 +231,7 @@ public class BOB {
|
||||
t.start();
|
||||
}
|
||||
} catch(IOException ioe) {
|
||||
warn("IOException on socket listen: " + ioe);
|
||||
error("IOException on socket listen: " + ioe);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -70,7 +70,7 @@ public class I2Plistener implements Runnable {
|
||||
boolean g = false;
|
||||
I2PSocket sessSocket = null;
|
||||
|
||||
serverSocket.setSoTimeout(1000);
|
||||
serverSocket.setSoTimeout(50);
|
||||
database.getReadLock();
|
||||
info.getReadLock();
|
||||
if(info.exists("INPORT")) {
|
||||
@ -107,32 +107,31 @@ public class I2Plistener implements Runnable {
|
||||
// System.out.println("Exception " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println("I2Plistener: Close");
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch(I2PException e) {
|
||||
// nop
|
||||
}
|
||||
|
||||
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
|
||||
// System.out.println("STOP Thread count " + Thread.activeCount());
|
||||
try {
|
||||
Thread.sleep(1000); //sleep for 1000 ms (One second)
|
||||
} catch(Exception e) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println("STOP Thread count " + Thread.activeCount());
|
||||
// need to kill off the socket manager too.
|
||||
I2PSession session = socketManager.getSession();
|
||||
if(session != null) {
|
||||
// System.out.println("I2Plistener: destroySession");
|
||||
try {
|
||||
session.destroySession();
|
||||
} catch(I2PSessionException ex) {
|
||||
// nop
|
||||
}
|
||||
// System.out.println("destroySession Thread count " + Thread.activeCount());
|
||||
}
|
||||
// System.out.println("I2Plistener: Waiting for children");
|
||||
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
|
||||
try {
|
||||
Thread.sleep(100); //sleep for 100 ms (One tenth second)
|
||||
} catch(Exception e) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println("I2Plistener: Done.");
|
||||
}
|
||||
}
|
||||
|
@ -119,20 +119,26 @@ die: {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println("I2PtoTCP: Going away...");
|
||||
} catch(Exception e) {
|
||||
// System.out.println("I2PtoTCP: Owch! damn!");
|
||||
break die;
|
||||
}
|
||||
} // die
|
||||
try {
|
||||
// System.out.println("I2PtoTCP: Close I2P");
|
||||
I2P.close();
|
||||
} catch(Exception e) {
|
||||
tell = false;
|
||||
}
|
||||
//System.out.println("I2PtoTCP: Closed I2P");
|
||||
try {
|
||||
// System.out.println("I2PtoTCP: Close sock");
|
||||
sock.close();
|
||||
} catch(Exception e) {
|
||||
tell = false;
|
||||
}
|
||||
// System.out.println("I2PtoTCP: Done");
|
||||
|
||||
}
|
||||
}
|
||||
|
56
apps/BOB/src/net/i2p/BOB/Lifted.java
Normal file
56
apps/BOB/src/net/i2p/BOB/Lifted.java
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
* Version 2, December 2004
|
||||
*
|
||||
* Copyright (C) sponge
|
||||
* Planet Earth
|
||||
* Everyone is permitted to copy and distribute verbatim or modified
|
||||
* copies of this license document, and changing it is allowed as long
|
||||
* as the name is changed.
|
||||
*
|
||||
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
*
|
||||
* 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
*
|
||||
* See...
|
||||
*
|
||||
* http://sam.zoy.org/wtfpl/
|
||||
* and
|
||||
* http://en.wikipedia.org/wiki/WTFPL
|
||||
*
|
||||
* ...for any additional details and liscense questions.
|
||||
*/
|
||||
package net.i2p.BOB;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Sets of "friendly" utilities to make life easier.
|
||||
* Any "Lifted" code will apear here, and credits given.
|
||||
* It's better to "Lift" a small chunk of "free" code than add in piles of
|
||||
* code we don't need, and don't want.
|
||||
*
|
||||
* @author sponge
|
||||
*/
|
||||
public class Lifted {
|
||||
|
||||
/**
|
||||
* Copy a set of properties from one Property to another.
|
||||
* Lifted from Apache Derby code svn repository.
|
||||
* Liscenced as follows:
|
||||
* http://svn.apache.org/repos/asf/db/derby/code/trunk/LICENSE
|
||||
*
|
||||
* @param src_prop Source set of properties to copy from.
|
||||
* @param dest_prop Dest Properties to copy into.
|
||||
*
|
||||
**/
|
||||
public static void copyProperties(Properties src_prop, Properties dest_prop) {
|
||||
for (Enumeration propertyNames = src_prop.propertyNames();
|
||||
propertyNames.hasMoreElements();) {
|
||||
Object key = propertyNames.nextElement();
|
||||
dest_prop.put(key, src_prop.get(key));
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.Log;
|
||||
import org.tanukisoftware.wrapper.WrapperManager;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -64,6 +65,7 @@ public class MUXlisten implements Runnable {
|
||||
MUXlisten(NamedDB database, NamedDB info, Log _log) throws I2PException, IOException, RuntimeException {
|
||||
int port = 0;
|
||||
InetAddress host = null;
|
||||
this.tg = null;
|
||||
this.database = database;
|
||||
this.info = info;
|
||||
this._log = _log;
|
||||
@ -72,7 +74,10 @@ public class MUXlisten implements Runnable {
|
||||
this.info.getReadLock();
|
||||
N = this.info.get("NICKNAME").toString();
|
||||
prikey = new ByteArrayInputStream((byte[])info.get("KEYS"));
|
||||
Properties Q = (Properties)info.get("PROPERTIES");
|
||||
// Make a new copy so that anything else won't muck with our database.
|
||||
Properties R = (Properties)info.get("PROPERTIES");
|
||||
Properties Q = new Properties();
|
||||
Lifted.copyProperties(R, Q);
|
||||
this.database.releaseReadLock();
|
||||
this.info.releaseReadLock();
|
||||
|
||||
@ -168,7 +173,7 @@ die: {
|
||||
boolean spin = true;
|
||||
while(spin) {
|
||||
try {
|
||||
Thread.sleep(1000); //sleep for 1000 ms (One second)
|
||||
Thread.sleep(200); //sleep for 200 ms (Two thenths second)
|
||||
} catch(InterruptedException e) {
|
||||
// nop
|
||||
}
|
||||
@ -207,23 +212,46 @@ die: {
|
||||
break die;
|
||||
}
|
||||
} // die
|
||||
|
||||
try {
|
||||
Thread.sleep(500); //sleep for 500 ms (One half second)
|
||||
} catch(InterruptedException ex) {
|
||||
// nop
|
||||
}
|
||||
// wait for child threads and thread groups to die
|
||||
while(tg.activeCount() + tg.activeGroupCount() != 0) {
|
||||
try {
|
||||
Thread.sleep(1000); //sleep for 1000 ms (One second)
|
||||
} catch(InterruptedException ex) {
|
||||
// nop
|
||||
// System.out.println("MUXlisten: waiting for children");
|
||||
if(tg.activeCount() + tg.activeGroupCount() != 0) {
|
||||
tg.interrupt(); // unwedge any blocking threads.
|
||||
while(tg.activeCount() + tg.activeGroupCount() != 0) {
|
||||
try {
|
||||
Thread.sleep(100); //sleep for 100 ms (One tenth second)
|
||||
} catch(InterruptedException ex) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
tg.destroy();
|
||||
// Zap reference to the ThreadGroup so the JVM can GC it.
|
||||
tg = null;
|
||||
} catch(Exception e) {
|
||||
// System.out.println("MUXlisten: Caught an exception" + e);
|
||||
break quit;
|
||||
}
|
||||
} // quit
|
||||
socketManager.destroySocketManager();
|
||||
// zero out everything, just incase.
|
||||
// This is here to catch when something fucks up REALLY bad.
|
||||
if(tg != null) {
|
||||
System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
|
||||
System.out.println("BOB: MUXlisten: Please email the following dump to sponge@mail.i2p");
|
||||
WrapperManager.requestThreadDump();
|
||||
System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
|
||||
System.out.println("BOB: MUXlisten: Please email the above dump to sponge@mail.i2p");
|
||||
}
|
||||
// zero out everything, just incase.
|
||||
try {
|
||||
socketManager.destroySocketManager();
|
||||
} catch(Exception e) {
|
||||
// nop
|
||||
}
|
||||
try {
|
||||
wlock();
|
||||
try {
|
||||
@ -236,7 +264,36 @@ die: {
|
||||
}
|
||||
wunlock();
|
||||
} catch(Exception e) {
|
||||
return;
|
||||
}
|
||||
// This is here to catch when something fucks up REALLY bad.
|
||||
if(tg != null) {
|
||||
if(tg.activeCount() + tg.activeGroupCount() != 0) {
|
||||
tg.interrupt(); // unwedge any blocking threads.
|
||||
while(tg.activeCount() + tg.activeGroupCount() != 0) {
|
||||
try {
|
||||
Thread.sleep(100); //sleep for 100 ms (One tenth second)
|
||||
} catch(InterruptedException ex) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
tg.destroy();
|
||||
// Zap reference to the ThreadGroup so the JVM can GC it.
|
||||
tg = null;
|
||||
}
|
||||
|
||||
// Lastly try to close things again.
|
||||
if(this.come_in) {
|
||||
try {
|
||||
listener.close();
|
||||
} catch(IOException e) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
socketManager.destroySocketManager();
|
||||
} catch(Exception e) {
|
||||
// nop
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@
|
||||
package net.i2p.BOB;
|
||||
|
||||
import net.i2p.client.streaming.RetransmissionTimer;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* Start from command line
|
||||
@ -39,8 +38,8 @@ public class Main {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// THINK THINK THINK THINK THINK THINK
|
||||
SimpleTimer Y = RetransmissionTimer.getInstance();
|
||||
RetransmissionTimer Y = RetransmissionTimer.getInstance();
|
||||
BOB.main(args);
|
||||
Y.removeSimpleTimer();
|
||||
Y.stop();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@
|
||||
*/
|
||||
package net.i2p.BOB;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@ -57,9 +56,28 @@ public class TCPio implements Runnable {
|
||||
* Copy from source to destination...
|
||||
* and yes, we are totally OK to block here on writes,
|
||||
* The OS has buffers, and I intend to use them.
|
||||
* We send an interrupt signal to the threadgroup to
|
||||
* unwedge any pending writes.
|
||||
*
|
||||
*/
|
||||
public void run() {
|
||||
/*
|
||||
* NOTE:
|
||||
* The write method of OutputStream calls the write method of
|
||||
* one argument on each of the bytes to be written out.
|
||||
* Subclasses are encouraged to override this method and provide
|
||||
* a more efficient implementation.
|
||||
*
|
||||
* So, is this really a performance problem?
|
||||
* Should we expand to several bytes?
|
||||
* I don't believe there would be any gain, since read method
|
||||
* has the same reccomendations. If anyone has a better way to
|
||||
* do this, I'm interested in performance improvements.
|
||||
*
|
||||
* --Sponge
|
||||
*
|
||||
*/
|
||||
|
||||
int b;
|
||||
byte a[] = new byte[1];
|
||||
boolean spin = true;
|
||||
@ -91,12 +109,16 @@ public class TCPio implements Runnable {
|
||||
* the stream has been reached.
|
||||
*
|
||||
*/
|
||||
// System.out.println("TCPio: End Of Stream");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// System.out.println("TCPio: RUNNING = false");
|
||||
} catch(Exception e) {
|
||||
// Eject!!! Eject!!!
|
||||
// System.out.println("TCPio: Caught an exception " + e);
|
||||
return;
|
||||
}
|
||||
// System.out.println("TCPio: Leaving.");
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,8 @@ public class TCPlistener implements Runnable {
|
||||
tgwatch = 2;
|
||||
}
|
||||
try {
|
||||
// System.out.println("Starting thread count " + Thread.activeCount());
|
||||
Socket server = new Socket();
|
||||
listener.setSoTimeout(1000);
|
||||
listener.setSoTimeout(50); // Half of the expected time from MUXlisten
|
||||
info.releaseReadLock();
|
||||
database.releaseReadLock();
|
||||
while(spin) {
|
||||
@ -87,7 +86,6 @@ public class TCPlistener implements Runnable {
|
||||
spin = info.get("RUNNING").equals(Boolean.TRUE);
|
||||
info.releaseReadLock();
|
||||
database.releaseReadLock();
|
||||
// System.out.println("Thread count " + Thread.activeCount());
|
||||
try {
|
||||
server = listener.accept();
|
||||
g = true;
|
||||
@ -102,8 +100,13 @@ public class TCPlistener implements Runnable {
|
||||
g = false;
|
||||
}
|
||||
}
|
||||
//System.out.println("TCPlistener: destroySession");
|
||||
listener.close();
|
||||
} catch(IOException ioe) {
|
||||
try {
|
||||
listener.close();
|
||||
} catch(IOException e) {
|
||||
}
|
||||
// Fatal failure, cause a stop event
|
||||
database.getReadLock();
|
||||
info.getReadLock();
|
||||
@ -120,17 +123,6 @@ public class TCPlistener implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.println("STOP!");
|
||||
|
||||
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
|
||||
// System.out.println("STOP Thread count " + Thread.activeCount());
|
||||
try {
|
||||
Thread.sleep(1000); //sleep for 1000 ms (One second)
|
||||
} catch(Exception e) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
// System.out.println("STOP Thread count " + Thread.activeCount());
|
||||
// need to kill off the socket manager too.
|
||||
I2PSession session = socketManager.getSession();
|
||||
if(session != null) {
|
||||
@ -139,8 +131,16 @@ public class TCPlistener implements Runnable {
|
||||
} catch(I2PSessionException ex) {
|
||||
// nop
|
||||
}
|
||||
// System.out.println("destroySession Thread count " + Thread.activeCount());
|
||||
}
|
||||
//System.out.println("TCPlistener: Waiting for children");
|
||||
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
|
||||
try {
|
||||
Thread.sleep(100); //sleep for 100 ms (One tenth second)
|
||||
} catch(Exception e) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
//System.out.println("TCPlistener: Done.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,7 @@ public class TCPtoI2P implements Runnable {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
// System.out.println("TCPtoI2P: Going away...");
|
||||
|
||||
} catch(I2PException e) {
|
||||
Emsg("ERROR " + e.toString(), out);
|
||||
@ -169,14 +170,17 @@ public class TCPtoI2P implements Runnable {
|
||||
} catch(IOException ioe) {
|
||||
}
|
||||
try {
|
||||
// System.out.println("TCPtoI2P: Close I2P");
|
||||
I2P.close();
|
||||
} catch(Exception e) {
|
||||
}
|
||||
|
||||
try {
|
||||
// System.out.println("TCPtoI2P: Close sock");
|
||||
sock.close();
|
||||
} catch(Exception e) {
|
||||
}
|
||||
// System.out.println("TCPtoI2P: Done.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,6 +179,11 @@ public class AddressBook {
|
||||
// IDN - basic check, not complete validation
|
||||
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
|
||||
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
|
||||
// Base32 spoofing (52chars.i2p)
|
||||
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
|
||||
// ... or maybe we do Base32 this way ...
|
||||
(! host.equals("b32.i2p")) &&
|
||||
(! host.endsWith(".b32.i2p")) &&
|
||||
// some reserved names that may be used for local configuration someday
|
||||
(! host.equals("proxy.i2p")) &&
|
||||
(! host.equals("router.i2p")) &&
|
||||
|
@ -1,347 +0,0 @@
|
||||
/*
|
||||
* bogobot - A simple join/part stats logger bot for I2P IRC.
|
||||
*
|
||||
* Bogobot.java
|
||||
* 2004 The I2P Project
|
||||
* http://www.i2p.net
|
||||
* This code is public domain.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.apache.log4j.DailyRollingFileAppender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
import org.jibble.pircbot.IrcException;
|
||||
import org.jibble.pircbot.NickAlreadyInUseException;
|
||||
import org.jibble.pircbot.PircBot;
|
||||
import org.jibble.pircbot.User;
|
||||
|
||||
/**
|
||||
* TODO 0.5 Add multi-server capability.
|
||||
*
|
||||
* @author hypercubus, oOo
|
||||
* @version 0.4
|
||||
*/
|
||||
public class Bogobot extends PircBot {
|
||||
|
||||
private static final String INTERVAL_DAILY = "daily";
|
||||
private static final String INTERVAL_MONTHLY = "monthly";
|
||||
private static final String INTERVAL_WEEKLY = "weekly";
|
||||
|
||||
private boolean _isIntentionalDisconnect = false;
|
||||
private long _lastUserlistCommandTimestamp = 0;
|
||||
private Logger _logger = Logger.getLogger(Bogobot.class);
|
||||
|
||||
private int _currentAutoRoundTripTag = 0;
|
||||
private long _lastAutoRoundTripSentTime = 0;
|
||||
private Timer _tickTimer;
|
||||
|
||||
private String _configFile;
|
||||
|
||||
private String _botPrimaryNick;
|
||||
private String _botSecondaryNick;
|
||||
private String _botNickservPassword;
|
||||
private String _botUsername;
|
||||
private String _ownerPrimaryNick;
|
||||
private String _ownerSecondaryNick;
|
||||
private String _botShutdownPassword;
|
||||
private String _ircChannel;
|
||||
private String _ircServer;
|
||||
private int _ircServerPort;
|
||||
private boolean _isLoggerEnabled;
|
||||
private String _loggedHostnamePattern;
|
||||
private boolean _isUserlistCommandEnabled;
|
||||
private String _logFilePrefix;
|
||||
private String _logFileRotationInterval;
|
||||
private long _commandAntiFloodInterval;
|
||||
private String _userlistCommandTrigger;
|
||||
private boolean _isRoundTripDelayEnabled;
|
||||
private int _roundTripDelayPeriod;
|
||||
|
||||
class BogobotTickTask extends TimerTask {
|
||||
private Bogobot _caller;
|
||||
|
||||
public BogobotTickTask(Bogobot caller) {
|
||||
_caller = caller;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_caller.onTick();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfigFile(String configFileName) {
|
||||
|
||||
_configFile = configFileName;
|
||||
|
||||
Properties config = new Properties();
|
||||
FileInputStream fis = null;
|
||||
|
||||
try {
|
||||
fis = new FileInputStream(configFileName);
|
||||
config.load(fis);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error loading configuration file");
|
||||
System.exit(2);
|
||||
|
||||
} finally {
|
||||
if (fis != null) try {
|
||||
fis.close();
|
||||
} catch (IOException ioe) { // nop
|
||||
}
|
||||
}
|
||||
|
||||
_botPrimaryNick = config.getProperty("botPrimaryNick", "somebot");
|
||||
_botSecondaryNick = config.getProperty("botSecondaryNick", "somebot_");
|
||||
_botNickservPassword = config.getProperty("botNickservPassword", "");
|
||||
_botUsername = config.getProperty("botUsername", "somebot");
|
||||
|
||||
_ownerPrimaryNick = config.getProperty("ownerPrimaryNick", "somenick");
|
||||
_ownerSecondaryNick = config.getProperty("ownerSecondaryNick", "somenick_");
|
||||
|
||||
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
|
||||
|
||||
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
|
||||
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
|
||||
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
|
||||
|
||||
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
|
||||
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
|
||||
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
|
||||
|
||||
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
|
||||
_roundTripDelayPeriod = Integer.parseInt(config.getProperty("roundTripDelayPeriod", "300"));
|
||||
|
||||
_isUserlistCommandEnabled = Boolean.valueOf(config.getProperty("isUserlistCommandEnabled", "true")).booleanValue();
|
||||
_userlistCommandTrigger = config.getProperty("userlistCommandTrigger", "!who");
|
||||
_commandAntiFloodInterval = Long.parseLong(config.getProperty("commandAntiFloodInterval", "60"));
|
||||
}
|
||||
|
||||
public Bogobot(String configFileName) {
|
||||
|
||||
loadConfigFile(configFileName);
|
||||
|
||||
this.setName(_botPrimaryNick);
|
||||
this.setLogin(_botUsername);
|
||||
_tickTimer = new Timer();
|
||||
_tickTimer.scheduleAtFixedRate(new BogobotTickTask(this), 1000, 10 * 1000);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Bogobot bogobot;
|
||||
|
||||
if (args.length > 1) {
|
||||
System.err.println("Too many arguments, the only allowed parameter is configuration file name");
|
||||
System.exit(3);
|
||||
}
|
||||
if (args.length == 1) {
|
||||
bogobot = new Bogobot(args[0]);
|
||||
} else {
|
||||
bogobot = new Bogobot("bogobot.config");
|
||||
}
|
||||
|
||||
bogobot.setVerbose(true);
|
||||
|
||||
if (bogobot._isLoggerEnabled)
|
||||
bogobot.initLogger();
|
||||
|
||||
bogobot.connectToServer();
|
||||
}
|
||||
|
||||
protected void onTick() {
|
||||
// Tick about once every ten seconds
|
||||
|
||||
if (this.isConnected() && _isRoundTripDelayEnabled) {
|
||||
if( ( (System.currentTimeMillis() - _lastAutoRoundTripSentTime) >= (_roundTripDelayPeriod * 1000) ) && (this.getOutgoingQueueSize() == 0) ) {
|
||||
// Connected, sending queue is empty and last RoundTrip is more then 5 minutes old -> Send a new one
|
||||
_currentAutoRoundTripTag ++;
|
||||
_lastAutoRoundTripSentTime = System.currentTimeMillis();
|
||||
sendNotice(this.getNick(),"ROUNDTRIP " + _currentAutoRoundTripTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDisconnect() {
|
||||
|
||||
if (_isIntentionalDisconnect)
|
||||
System.exit(0);
|
||||
|
||||
if (_isLoggerEnabled)
|
||||
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " *** (Lost connection)");
|
||||
|
||||
try {
|
||||
Thread.sleep(60000);
|
||||
} catch (InterruptedException e) {
|
||||
// No worries.
|
||||
}
|
||||
connectToServer();
|
||||
}
|
||||
|
||||
protected void onJoin(String channel, String sender, String login, String hostname) {
|
||||
|
||||
if (_isLoggerEnabled) {
|
||||
if (sender.equals(this.getName())) {
|
||||
|
||||
_logger.info(System.currentTimeMillis() + " joins *** " + _botPrimaryNick + " ***");
|
||||
|
||||
} else {
|
||||
|
||||
String prependedHostname = "@" + hostname;
|
||||
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
|
||||
_logger.info(System.currentTimeMillis() + " joins " + sender);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMessage(String channel, String sender, String login, String hostname, String message) {
|
||||
message = message.replaceFirst("<.+?> ", "");
|
||||
if (_isUserlistCommandEnabled && message.equals(_userlistCommandTrigger)) {
|
||||
|
||||
if (System.currentTimeMillis() - _lastUserlistCommandTimestamp < _commandAntiFloodInterval * 1000)
|
||||
return;
|
||||
|
||||
Object[] users = getUsers(_ircChannel);
|
||||
String output = "Userlist for " + _ircChannel + ": ";
|
||||
|
||||
for (int i = 0; i < users.length; i++)
|
||||
output += "[" + ((User) users[i]).getNick() + "] ";
|
||||
|
||||
sendMessage(_ircChannel, output);
|
||||
_lastUserlistCommandTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPart(String channel, String sender, String login, String hostname) {
|
||||
|
||||
if (_isLoggerEnabled) {
|
||||
if (sender.equals(this.getName())) {
|
||||
_logger.info(System.currentTimeMillis() + " parts *** " + _botPrimaryNick + " ***");
|
||||
} else {
|
||||
String prependedHostname = "@" + hostname;
|
||||
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
|
||||
_logger.info(System.currentTimeMillis() + " parts " + sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void onPrivateMessage(String sender, String login, String hostname, String message) {
|
||||
/*
|
||||
* Nobody else except the bot's owner can shut it down, unless of
|
||||
* course the owner's nick isn't registered and someone's spoofing it.
|
||||
*/
|
||||
if ((sender.equals(_ownerPrimaryNick) || sender.equals(_ownerSecondaryNick)) && message.equals(_botShutdownPassword)) {
|
||||
|
||||
if (_isLoggerEnabled)
|
||||
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " ***");
|
||||
|
||||
_isIntentionalDisconnect = true;
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {
|
||||
String prependedHostname = "@" + sourceHostname;
|
||||
|
||||
if (sourceNick.equals(_botPrimaryNick))
|
||||
changeNick(_botPrimaryNick);
|
||||
|
||||
if (_isLoggerEnabled) {
|
||||
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
|
||||
_logger.info(System.currentTimeMillis() + " quits " + sourceNick + " " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void connectToServer() {
|
||||
|
||||
int loginAttempts = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
connect(_ircServer, _ircServerPort);
|
||||
break;
|
||||
} catch (NickAlreadyInUseException e) {
|
||||
if (loginAttempts == 1) {
|
||||
System.out.println("Sorry, the primary and secondary bot nicks are already taken. Exiting.");
|
||||
System.exit(1);
|
||||
}
|
||||
loginAttempts++;
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e1) {
|
||||
// Hmph.
|
||||
}
|
||||
|
||||
if (getName().equals(_botPrimaryNick))
|
||||
setName(_botSecondaryNick);
|
||||
else
|
||||
setName(_botPrimaryNick);
|
||||
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
System.out.println("Error during login: ");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
} catch (IrcException e) {
|
||||
System.out.println("Error during login: ");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
joinChannel(_ircChannel);
|
||||
}
|
||||
|
||||
protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) {
|
||||
|
||||
if (sourceNick.equals("NickServ") && (notice.indexOf("/msg NickServ IDENTIFY") >= 0) && (_botNickservPassword != "")) {
|
||||
sendRawLineViaQueue("NICKSERV IDENTIFY " + _botNickservPassword);
|
||||
}
|
||||
|
||||
if (sourceNick.equals(getNick()) && notice.equals( "ROUNDTRIP " + _currentAutoRoundTripTag)) {
|
||||
int delay = (int)((System.currentTimeMillis() - _lastAutoRoundTripSentTime) / 100);
|
||||
// sendMessage(_ircChannel, "Round-trip delay = " + (delay / 10.0f) + " seconds");
|
||||
if (_isLoggerEnabled)
|
||||
_logger.info(System.currentTimeMillis() + " roundtrip " + delay);
|
||||
}
|
||||
}
|
||||
|
||||
private void initLogger() {
|
||||
|
||||
String logFilePath = "logs" + File.separator + _logFilePrefix;
|
||||
DailyRollingFileAppender rollingFileAppender = null;
|
||||
|
||||
if (!(new File("logs").exists()))
|
||||
(new File("logs")).mkdirs();
|
||||
|
||||
try {
|
||||
|
||||
if (_logFileRotationInterval.equals("monthly"))
|
||||
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM'.log'");
|
||||
else if (_logFileRotationInterval.equals("weekly"))
|
||||
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-ww'.log'");
|
||||
else
|
||||
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM-dd'.log'");
|
||||
|
||||
rollingFileAppender.setThreshold(Level.INFO);
|
||||
_logger.addAppender(rollingFileAppender);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Error: Couldn't create or open an existing log file. Exiting.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,353 +0,0 @@
|
||||
/*
|
||||
* bogoparser - A simple logfile analyzer for bogobot.
|
||||
*
|
||||
* Bogoparser.java
|
||||
* 2004 The I2P Project
|
||||
* http://www.i2p.net
|
||||
* This code is public domain.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author hypercubus
|
||||
* @version 0.4
|
||||
*/
|
||||
public class Bogoparser {
|
||||
|
||||
private static void displayUsageAndExit() {
|
||||
System.out.println("\r\nUsage:\r\n\r\n java Bogoparser [--by-duration] <logfile>\r\n");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Bogoparser bogoparser;
|
||||
|
||||
if (args.length < 1 || args.length > 2)
|
||||
displayUsageAndExit();
|
||||
|
||||
if (args.length == 2) {
|
||||
if (!args[0].equals("--by-duration"))
|
||||
displayUsageAndExit();
|
||||
bogoparser = new Bogoparser(args[1], true);
|
||||
}
|
||||
|
||||
if (args.length == 1)
|
||||
bogoparser = new Bogoparser(args[0], false);
|
||||
}
|
||||
|
||||
private Bogoparser(String logfile, boolean sortByDuration) {
|
||||
|
||||
ArrayList sortedSessions;
|
||||
|
||||
if (sortByDuration) {
|
||||
sortedSessions = sortSessionsByDuration(calculateSessionDurations(sortSessionsByTime(readLogfile(logfile))));
|
||||
formatAndOutputByDuration(sortedSessions);
|
||||
} else {
|
||||
sortedSessions = calculateSessionDurations(sortSessionsByQuitReason(sortSessionsByNick(sortSessionsByTime(readLogfile(logfile)))));
|
||||
formatAndOutput(sortedSessions);
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList calculateSessionDurations(ArrayList sortedSessionsByQuitReasonOrDuration) {
|
||||
|
||||
ArrayList calculatedSessionDurations = new ArrayList();
|
||||
|
||||
for (int i = 0; i+1 < sortedSessionsByQuitReasonOrDuration.size(); i += 2) {
|
||||
|
||||
String joinsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i);
|
||||
String[] joinsEntryFields = joinsEntry.split(" ");
|
||||
|
||||
String quitsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i+1);
|
||||
Pattern p = Pattern.compile("^([^ ]+) [^ ]+ ([^ ]+) (.*)$");
|
||||
Matcher m = p.matcher(quitsEntry);
|
||||
|
||||
if (m.matches()) {
|
||||
|
||||
String currentJoinTime = joinsEntryFields[0];
|
||||
String currentNick = m.group(2);
|
||||
String currentQuitReason = m.group(3);
|
||||
String currentQuitTime = m.group(1);
|
||||
long joinsTimeInMilliseconds;
|
||||
long quitsTimeInMilliseconds;
|
||||
long sessionLengthInMilliseconds;
|
||||
|
||||
joinsTimeInMilliseconds = Long.parseLong(currentJoinTime);
|
||||
quitsTimeInMilliseconds = Long.parseLong(currentQuitTime);
|
||||
sessionLengthInMilliseconds = quitsTimeInMilliseconds - joinsTimeInMilliseconds;
|
||||
|
||||
String hours = "" + sessionLengthInMilliseconds/1000/60/60;
|
||||
String minutes = "" + (sessionLengthInMilliseconds/1000/60)%60;
|
||||
|
||||
if (hours.length() < 2)
|
||||
hours = "0" + hours;
|
||||
|
||||
if (hours.length() < 3)
|
||||
hours = "0" + hours;
|
||||
|
||||
if (minutes.length() < 2)
|
||||
minutes = "0" + minutes;
|
||||
|
||||
int columnPadding = 19-currentNick.length();
|
||||
String columnPaddingString = " ";
|
||||
|
||||
for (int j = 0; j < columnPadding; j++)
|
||||
columnPaddingString = columnPaddingString + " ";
|
||||
|
||||
calculatedSessionDurations.add(sessionLengthInMilliseconds + " " + currentNick + columnPaddingString + " online " + hours + " hours " + minutes + " minutes " + currentQuitReason);
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + quitsEntry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
return calculatedSessionDurations;
|
||||
}
|
||||
|
||||
private void formatAndOutput(ArrayList sortedSessions) {
|
||||
|
||||
String quitReason = null;
|
||||
|
||||
for (int i = 0; i < sortedSessions.size(); i++) {
|
||||
|
||||
String entry = (String) sortedSessions.get(i);
|
||||
Pattern p = Pattern.compile("^[\\d]+ ([^ ]+ +online [\\d]+ hours [\\d]+ minutes) (.*)$");
|
||||
Matcher m = p.matcher(entry);
|
||||
|
||||
if (m.matches()) {
|
||||
|
||||
if (quitReason == null) {
|
||||
quitReason = m.group(2);
|
||||
System.out.println("\r\nQUIT: " + ((m.group(2).equals("")) ? "No Reason Given" : quitReason) + "\r\n");
|
||||
}
|
||||
|
||||
String tempQuitReason = m.group(2);
|
||||
String tempSession = m.group(1);
|
||||
|
||||
if (tempQuitReason.equals(quitReason)) {
|
||||
System.out.println(" " + tempSession);
|
||||
} else {
|
||||
quitReason = null;
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
System.out.println("\r\n");
|
||||
}
|
||||
|
||||
private void formatAndOutputByDuration(ArrayList sortedSessions) {
|
||||
System.out.println("\r\n");
|
||||
|
||||
for (int i = 0; i < sortedSessions.size(); i++) {
|
||||
String[] columns = ((String) sortedSessions.get(i)).split(" ", 2);
|
||||
System.out.println(columns[1]);
|
||||
}
|
||||
|
||||
System.out.println("\r\n");
|
||||
}
|
||||
|
||||
private ArrayList readLogfile(String logfile) {
|
||||
|
||||
ArrayList log = new ArrayList();
|
||||
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(logfile)));
|
||||
|
||||
for (String line; (line = in.readLine()) != null; )
|
||||
log.add(line);
|
||||
|
||||
in.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("\r\nError: Can't find logfile '" + logfile + "'.\r\n");
|
||||
System.exit(1);
|
||||
|
||||
} catch (IOException e) {
|
||||
System.out.println("\r\nError: Can't read logfile '" + logfile + "'.\r\n");
|
||||
System.exit(1);
|
||||
}
|
||||
return log;
|
||||
}
|
||||
|
||||
/*
|
||||
* Performs an odd-even transposition sort.
|
||||
*/
|
||||
private ArrayList sortSessionsByDuration(ArrayList calculatedSessionDurations) {
|
||||
|
||||
for (int i = 0; i < calculatedSessionDurations.size()/2; i++) {
|
||||
for (int j = 0; j+1 < calculatedSessionDurations.size(); j += 2) {
|
||||
|
||||
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
|
||||
long currentDuration = Long.parseLong(currentDurationString[0]);
|
||||
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
|
||||
long nextDuration = Long.parseLong(nextDurationString[0]);
|
||||
|
||||
if (currentDuration > nextDuration) {
|
||||
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
|
||||
calculatedSessionDurations.remove(j+2);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 1; j+1 < calculatedSessionDurations.size(); j += 2) {
|
||||
|
||||
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
|
||||
long currentDuration = Long.parseLong(currentDurationString[0]);
|
||||
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
|
||||
long nextDuration = Long.parseLong(nextDurationString[0]);
|
||||
|
||||
if (currentDuration > nextDuration) {
|
||||
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
|
||||
calculatedSessionDurations.remove(j+2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return calculatedSessionDurations;
|
||||
}
|
||||
|
||||
private ArrayList sortSessionsByNick(ArrayList sortedSessionsByTime) {
|
||||
|
||||
ArrayList sortedSessionsByNick = new ArrayList();
|
||||
|
||||
while (sortedSessionsByTime.size() != 0) {
|
||||
|
||||
String entry = (String) sortedSessionsByTime.get(0);
|
||||
String[] entryFields = entry.split(" ");
|
||||
String currentNick = entryFields[2];
|
||||
|
||||
sortedSessionsByNick.add(entry);
|
||||
sortedSessionsByNick.add(sortedSessionsByTime.get(1));
|
||||
sortedSessionsByTime.remove(0);
|
||||
sortedSessionsByTime.remove(0);
|
||||
for (int i = 0; i+1 < sortedSessionsByTime.size(); i += 2) {
|
||||
|
||||
String nextEntry = (String) sortedSessionsByTime.get(i);
|
||||
String[] nextEntryFields = nextEntry.split(" ");
|
||||
|
||||
if (nextEntryFields[2].equals(currentNick)) {
|
||||
sortedSessionsByNick.add(nextEntry);
|
||||
sortedSessionsByNick.add(sortedSessionsByTime.get(i+1));
|
||||
sortedSessionsByTime.remove(i);
|
||||
sortedSessionsByTime.remove(i);
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sortedSessionsByNick;
|
||||
}
|
||||
|
||||
private ArrayList sortSessionsByQuitReason(ArrayList sortedSessionsByNick) {
|
||||
|
||||
ArrayList sortedSessionsByQuitReason = new ArrayList();
|
||||
|
||||
while (sortedSessionsByNick.size() != 0) {
|
||||
|
||||
String entry = (String) sortedSessionsByNick.get(1);
|
||||
Pattern p = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
|
||||
Matcher m = p.matcher(entry);
|
||||
|
||||
if (m.matches()) {
|
||||
|
||||
String currentQuitReason = m.group(1);
|
||||
|
||||
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(0));
|
||||
sortedSessionsByQuitReason.add(entry);
|
||||
sortedSessionsByNick.remove(0);
|
||||
sortedSessionsByNick.remove(0);
|
||||
for (int i = 0; i+1 < sortedSessionsByNick.size(); i += 2) {
|
||||
|
||||
String nextEntry = (String) sortedSessionsByNick.get(i+1);
|
||||
Pattern p2 = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
|
||||
Matcher m2 = p2.matcher(nextEntry);
|
||||
|
||||
if (m2.matches()) {
|
||||
|
||||
String nextQuitReason = m2.group(1);
|
||||
|
||||
if (nextQuitReason.equals(currentQuitReason)) {
|
||||
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(i));
|
||||
sortedSessionsByQuitReason.add(nextEntry);
|
||||
sortedSessionsByNick.remove(i);
|
||||
sortedSessionsByNick.remove(i);
|
||||
i -= 2;
|
||||
}
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + nextEntry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
return sortedSessionsByQuitReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sessions terminated with "parts" messages instead of "quits" are filtered
|
||||
* out.
|
||||
*/
|
||||
private ArrayList sortSessionsByTime(ArrayList log) {
|
||||
|
||||
ArrayList sortedSessionsByTime = new ArrayList();
|
||||
|
||||
mainLoop:
|
||||
while (log.size() > 0) {
|
||||
|
||||
String entry = (String) log.get(0);
|
||||
String[] entryFields = entry.split(" ");
|
||||
|
||||
if (entryFields[1].equals("quits") && !entryFields[1].equals("joins")) {
|
||||
/*
|
||||
* Discard entry. The specified log either doesn't contain
|
||||
* the corresponding "joins" time for this quit entry or the
|
||||
* entry is a "parts" or unknown message, and in both cases
|
||||
* the entry's data is useless.
|
||||
*/
|
||||
log.remove(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 1; i < log.size(); i++) { // Find corresponding "quits" entry.
|
||||
|
||||
String tempEntry = (String) log.get(i);
|
||||
String[] tempEntryFields = tempEntry.split(" ");
|
||||
|
||||
if (tempEntryFields[2].equals(entryFields[2])) { // Check if the nick fields for the two entries match.
|
||||
if (!tempEntryFields[1].equals("quits")) {
|
||||
if (tempEntryFields[1].equals("joins")) { // Don't discard a subsequent "joins" entry.
|
||||
log.remove(0);
|
||||
continue mainLoop;
|
||||
}
|
||||
log.remove(i);
|
||||
continue;
|
||||
}
|
||||
sortedSessionsByTime.add(entry);
|
||||
sortedSessionsByTime.add(tempEntry);
|
||||
log.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Discard "joins" entry. The specified log doesn't contain the
|
||||
* corresponding "quits" time for this entry so the entry's
|
||||
* data is useless.
|
||||
*/
|
||||
|
||||
log.remove(0);
|
||||
}
|
||||
|
||||
return sortedSessionsByTime;
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* ============================================================================
|
||||
* The Apache Software License, Version 1.1
|
||||
* ============================================================================
|
||||
*
|
||||
* Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if any, must
|
||||
* include the following acknowledgment: "This product includes software
|
||||
* developed by the Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowledgment may appear in the software itself, if
|
||||
* and wherever such third-party acknowledgments normally appear.
|
||||
*
|
||||
* 4. The names "log4j" and "Apache Software Foundation" must not be used to
|
||||
* endorse or promote products derived from this software without prior
|
||||
* written permission. For written permission, please contact
|
||||
* apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache", nor may
|
||||
* "Apache" appear in their name, without prior written permission of the
|
||||
* Apache Software Foundation.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
|
||||
* DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* on behalf of the Apache Software Foundation. For more information on the
|
||||
* Apache Software Foundation, please see <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
@ -1 +0,0 @@
|
||||
java -cp .;log4j-1.2.8.jar;pircbot.jar Bogobot
|
@ -1,101 +0,0 @@
|
||||
#####
|
||||
# Bogobot user configuration
|
||||
#####
|
||||
|
||||
###
|
||||
# The bot's nick and backup nick. You will probably want to register these with
|
||||
# the IRC server's NickServ.(a NickServ interface is forthcoming).
|
||||
#
|
||||
botPrimaryNick=somebot
|
||||
botSecondaryNick=somebot_
|
||||
|
||||
###
|
||||
# The bot's password required by Nickserv service's identify command.
|
||||
# You have to register the nickname yourself first, the bot will not.
|
||||
#
|
||||
botNickservPassword=
|
||||
|
||||
###
|
||||
# The bot's username. Appears in the whois replies
|
||||
#
|
||||
botUsername=somebot
|
||||
|
||||
#####
|
||||
# The bot owner's nick and backup nick. One of these must match the owner's
|
||||
# currently-used nick or else remote shutdown will not be possible. You will
|
||||
# probably want to register these with the IRC server's NickServ.
|
||||
#
|
||||
ownerPrimaryNick=somenick
|
||||
ownerSecondaryNick=somenick_
|
||||
|
||||
###
|
||||
# The bot will disconnect and shut down when sent this password via private
|
||||
# message (aka query) from either of the owner nicks specified above. DO NOT USE
|
||||
# THIS DEFAULT VALUE!
|
||||
#
|
||||
botShutdownPassword=take off eh
|
||||
|
||||
###
|
||||
# The server, channel, and port the bot will connect to.
|
||||
#
|
||||
ircChannel=#i2p-chat
|
||||
ircServer=irc.duck.i2p
|
||||
ircServerPort=6668
|
||||
|
||||
###
|
||||
# Set to "true" to enable logging, else "false" (but don't use quotation marks).
|
||||
#
|
||||
isLoggerEnabled=true
|
||||
|
||||
###
|
||||
# Restrict logging of joins and parts on the user hostname.
|
||||
# Leave empty to log all of them
|
||||
# Prepend with a @ for a perfect match
|
||||
# Otherwise, specify the required end of the user hostname
|
||||
#
|
||||
loggedHostnamePattern=@free.duck.i2p
|
||||
|
||||
###
|
||||
# The prefix to be used for the filenames of logs.
|
||||
#
|
||||
logFilePrefix=irc.duck.i2p.i2p-chat
|
||||
|
||||
###
|
||||
# How often the logs should be rotated. Either "daily", "weekly", or "monthly"
|
||||
# (but don't use quotation marks).
|
||||
#
|
||||
logFileRotationInterval=daily
|
||||
|
||||
###
|
||||
# Set to "true" to enable the regular round-trip delay computation,
|
||||
# else "false" (but don't use quotation marks).
|
||||
#
|
||||
isRoundTripDelayEnabled=false
|
||||
|
||||
###
|
||||
# How often should the round-trip delay be recorded.
|
||||
# (in seconds)
|
||||
#
|
||||
roundTripDelayPeriod=300
|
||||
|
||||
###
|
||||
# Set to "true" to enable the userlist command, else "false" (but don't use
|
||||
# quotation marks).
|
||||
#
|
||||
isUserlistCommandEnabled=true
|
||||
|
||||
###
|
||||
# The userlist trigger command to listen for. It is a good idea to prefix
|
||||
# triggers with some non-alphanumeric character in order to avoid accidental
|
||||
# trigger use during normal channel conversation. In most cases you will
|
||||
# probably want to choose a unique trigger here that no other bots in the
|
||||
# channel will respond to.
|
||||
#
|
||||
userlistCommandTrigger=!who
|
||||
|
||||
###
|
||||
# The number of seconds to rest after replying to a userlist command issued by
|
||||
# a user in the channel. The bot will ignore subsequent userlist commands during
|
||||
# this period. This helps prevent flooding.
|
||||
#
|
||||
commandAntiFloodInterval=60
|
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
java -cp .:log4j-1.2.8.jar:pircbot.jar Bogobot
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ********************************************************** -->
|
||||
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
|
||||
<!-- -->
|
||||
<!-- build-eclipse.xml -->
|
||||
<!-- 2004 The I2P Project -->
|
||||
<!-- http://www.i2p.net -->
|
||||
<!-- This code is public domain. -->
|
||||
<!-- -->
|
||||
<!-- authors: hypercubus, oOo -->
|
||||
<!-- version 0.4 -->
|
||||
<!-- ********************************************************** -->
|
||||
|
||||
<project basedir="." default="dist" name="Bogobot">
|
||||
|
||||
<!-- init:
|
||||
Create distribution directory if missing and initialize time stamp for
|
||||
archive naming -->
|
||||
<target name="init">
|
||||
<mkdir dir="dist" />
|
||||
<tstamp>
|
||||
<format pattern="yyyy-MM-dd" property="DSTAMP" />
|
||||
</tstamp>
|
||||
</target>
|
||||
|
||||
<!-- dist.bin:
|
||||
Create the binary distribution archive -->
|
||||
<target depends="init" description="Create the binary distribution archive" name="dist.bin">
|
||||
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist.source:
|
||||
Create the source distribution archive -->
|
||||
<target depends="init" description="Create the source distribution archive" name="dist.source">
|
||||
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist:
|
||||
Create both the binary and source distribution archives -->
|
||||
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
|
||||
<echo message="Successfully created binary and source distribution archives in directory 'dist'." />
|
||||
</target>
|
||||
|
||||
<!-- clean:
|
||||
Delete all class files and temporary directories -->
|
||||
<target description="Delete all class files and temporary directories" name="clean">
|
||||
<delete>
|
||||
<fileset dir="${basedir}" includes="**/*.class" />
|
||||
</delete>
|
||||
<echo message="Clean successful." />
|
||||
</target>
|
||||
|
||||
</project>
|
@ -1,64 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ********************************************************** -->
|
||||
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
|
||||
<!-- -->
|
||||
<!-- build.xml -->
|
||||
<!-- 2004 The I2P Project -->
|
||||
<!-- http://www.i2p.net -->
|
||||
<!-- This code is public domain. -->
|
||||
<!-- -->
|
||||
<!-- authors: hypercubus, oOo -->
|
||||
<!-- version 0.4 -->
|
||||
<!-- ********************************************************** -->
|
||||
|
||||
<project basedir="." default="compile" name="Bogobot">
|
||||
|
||||
<!-- init:
|
||||
Create distribution directory if missing and initialize time stamp for
|
||||
archive naming -->
|
||||
<target name="init">
|
||||
<mkdir dir="dist" />
|
||||
<tstamp>
|
||||
<format pattern="yyyy-MM-dd" property="DSTAMP" />
|
||||
</tstamp>
|
||||
</target>
|
||||
|
||||
<!-- compile:
|
||||
Compile source code -->
|
||||
<target depends="init" description="Compile source code" name="compile">
|
||||
<javac classpath="${basedir};log4j-1.2.8.jar;pircbot.jar" source="1.4" srcdir="." />
|
||||
</target>
|
||||
|
||||
<!-- dist.bin:
|
||||
Create the binary distribution archive -->
|
||||
<target depends="init,compile" description="Create the binary distribution archive" name="dist.bin">
|
||||
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class Bogobot$BogobotTickTask.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist.source:
|
||||
Create the source distribution archive -->
|
||||
<target depends="init" description="Create the source distribution archive" name="dist.source">
|
||||
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist:
|
||||
Create both the binary and source distribution archives -->
|
||||
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
|
||||
<echo message="Successfully created binary and source distribution archives in directory 'dist'." />
|
||||
</target>
|
||||
|
||||
<!-- clean:
|
||||
Delete all class files and temporary directories -->
|
||||
<target description="Delete all class files and temporary directories" name="clean">
|
||||
<delete>
|
||||
<fileset dir="${basedir}" includes="**/*.class" />
|
||||
</delete>
|
||||
<echo message="Clean successful." />
|
||||
</target>
|
||||
|
||||
</project>
|
Binary file not shown.
Binary file not shown.
1
apps/i2psnark/i2psnark.config
Normal file
1
apps/i2psnark/i2psnark.config
Normal file
@ -0,0 +1 @@
|
||||
i2psnark.dir=i2psnark
|
@ -28,7 +28,7 @@ import java.io.OutputStream;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -36,17 +36,16 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
private static final ConnectionAcceptor _instance = new ConnectionAcceptor();
|
||||
public static final ConnectionAcceptor instance() { return _instance; }
|
||||
private Log _log = new Log(ConnectionAcceptor.class);
|
||||
private I2PServerSocket serverSocket;
|
||||
private PeerAcceptor peeracceptor;
|
||||
private Thread thread;
|
||||
private I2PSnarkUtil _util;
|
||||
|
||||
private boolean stop;
|
||||
private boolean socketChanged;
|
||||
|
||||
private ConnectionAcceptor() {}
|
||||
public ConnectionAcceptor(I2PSnarkUtil util) { _util = util; }
|
||||
|
||||
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
|
||||
if (serverSocket != socket) {
|
||||
@ -56,29 +55,30 @@ public class ConnectionAcceptor implements Runnable
|
||||
stop = false;
|
||||
socketChanged = true;
|
||||
if (thread == null) {
|
||||
thread = new I2PThread(this, "I2PSnark acceptor");
|
||||
thread = new I2PAppThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionAcceptor(I2PServerSocket serverSocket,
|
||||
public ConnectionAcceptor(I2PSnarkUtil util, I2PServerSocket serverSocket,
|
||||
PeerAcceptor peeracceptor)
|
||||
{
|
||||
this.serverSocket = serverSocket;
|
||||
this.peeracceptor = peeracceptor;
|
||||
_util = util;
|
||||
|
||||
socketChanged = false;
|
||||
stop = false;
|
||||
thread = new I2PThread(this, "I2PSnark acceptor");
|
||||
thread = new I2PAppThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void halt()
|
||||
{
|
||||
if (true) throw new RuntimeException("wtf");
|
||||
if (stop) return;
|
||||
stop = true;
|
||||
|
||||
I2PServerSocket ss = serverSocket;
|
||||
@ -95,7 +95,7 @@ public class ConnectionAcceptor implements Runnable
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
serverSocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
serverSocket = _util.getServerSocket();
|
||||
socketChanged = true;
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
@ -116,7 +116,7 @@ public class ConnectionAcceptor implements Runnable
|
||||
socketChanged = false;
|
||||
}
|
||||
while ( (serverSocket == null) && (!stop)) {
|
||||
serverSocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
serverSocket = _util.getServerSocket();
|
||||
if (serverSocket == null)
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
@ -129,29 +129,30 @@ public class ConnectionAcceptor implements Runnable
|
||||
if (socketChanged) {
|
||||
continue;
|
||||
} else {
|
||||
I2PServerSocket ss = I2PSnarkUtil.instance().getServerSocket();
|
||||
I2PServerSocket ss = _util.getServerSocket();
|
||||
if (ss != serverSocket) {
|
||||
serverSocket = ss;
|
||||
socketChanged = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
|
||||
Thread t = new I2PAppThread(new Handler(socket), "Connection-" + socket);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
catch (I2PException ioe)
|
||||
{
|
||||
if (!socketChanged) {
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
_util.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
_util.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
// catch oom?
|
||||
}
|
||||
|
||||
try
|
||||
@ -161,7 +162,6 @@ public class ConnectionAcceptor implements Runnable
|
||||
}
|
||||
catch (I2PException ignored) { }
|
||||
|
||||
throw new RuntimeException("wtf");
|
||||
}
|
||||
|
||||
private class Handler implements Runnable {
|
||||
|
@ -30,4 +30,8 @@ public interface CoordinatorListener
|
||||
* Called when the PeerCoordinator notices a change in the state of a peer.
|
||||
*/
|
||||
void peerChange(PeerCoordinator coordinator, Peer peer);
|
||||
|
||||
public boolean overUploadLimit(int uploaders);
|
||||
public boolean overUpBWLimit();
|
||||
public boolean overUpBWLimit(long total);
|
||||
}
|
||||
|
@ -2,12 +2,15 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -20,17 +23,20 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* I2P specific helpers for I2PSnark
|
||||
* We use this class as a sort of context for i2psnark
|
||||
* so we can run multiple instances of single Snarks
|
||||
* (but not multiple SnarkManagers, it is still static)
|
||||
*/
|
||||
public class I2PSnarkUtil {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private static I2PSnarkUtil _instance = new I2PSnarkUtil();
|
||||
public static I2PSnarkUtil instance() { return _instance; }
|
||||
|
||||
private boolean _shouldProxy;
|
||||
private String _proxyHost;
|
||||
@ -43,9 +49,18 @@ public class I2PSnarkUtil {
|
||||
private Set _shitlist;
|
||||
private int _maxUploaders;
|
||||
private int _maxUpBW;
|
||||
private int _maxConnections;
|
||||
private File _tmpDir;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
|
||||
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
|
||||
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
|
||||
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
|
||||
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
|
||||
public static final int MAX_CONNECTIONS = 16; // per torrent
|
||||
|
||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_opts = new HashMap();
|
||||
setProxy("127.0.0.1", 4444);
|
||||
@ -53,6 +68,14 @@ public class I2PSnarkUtil {
|
||||
_shitlist = new HashSet(64);
|
||||
_configured = false;
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
_maxConnections = MAX_CONNECTIONS;
|
||||
// This is used for both announce replies and .torrent file downloads,
|
||||
// so it must be available even if not connected to I2CP.
|
||||
// so much for multiple instances
|
||||
_tmpDir = new File("tmp", "i2psnark");
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,8 +99,11 @@ public class I2PSnarkUtil {
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
if (i2cpHost != null)
|
||||
_i2cpHost = i2cpHost;
|
||||
if (i2cpPort > 0)
|
||||
_i2cpPort = i2cpPort;
|
||||
// can't remove any options this way...
|
||||
if (opts != null)
|
||||
_opts.putAll(opts);
|
||||
_configured = true;
|
||||
@ -93,6 +119,11 @@ public class I2PSnarkUtil {
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public void setMaxConnections(int limit) {
|
||||
_maxConnections = limit;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
@ -101,6 +132,7 @@ public class I2PSnarkUtil {
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
public int getMaxUploaders() { return _maxUploaders; }
|
||||
public int getMaxUpBW() { return _maxUpBW; }
|
||||
public int getMaxConnections() { return _maxConnections; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
@ -144,6 +176,10 @@ public class I2PSnarkUtil {
|
||||
_manager = null;
|
||||
_shitlist.clear();
|
||||
mgr.destroySocketManager();
|
||||
// this will delete a .torrent file d/l in progress so don't do that...
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
// in case the user will d/l a .torrent file next...
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
|
||||
/** connect to the given destination */
|
||||
@ -161,7 +197,7 @@ public class I2PSnarkUtil {
|
||||
synchronized (_shitlist) {
|
||||
_shitlist.add(dest);
|
||||
}
|
||||
SimpleTimer.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
|
||||
SimpleScheduler.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
}
|
||||
}
|
||||
@ -182,13 +218,15 @@ public class I2PSnarkUtil {
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
File out = null;
|
||||
try {
|
||||
out = File.createTempFile("i2psnark", "url", new File("."));
|
||||
// we could use the system tmp dir but deleteOnExit() doesn't seem to work on all platforms...
|
||||
out = File.createTempFile("i2psnark", null, _tmpDir);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
if (out != null)
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
out.deleteOnExit();
|
||||
String fetchURL = url;
|
||||
if (rewrite)
|
||||
fetchURL = rewriteAnnounce(url);
|
||||
@ -223,6 +261,28 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/** Base64 only - static (no naming service) */
|
||||
static Destination getDestinationFromBase64(String ip) {
|
||||
if (ip == null) return null;
|
||||
if (ip.endsWith(".i2p")) {
|
||||
if (ip.length() < 520)
|
||||
return null;
|
||||
try {
|
||||
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return new Destination(ip);
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Base64 Hash or Hash.i2p or name.i2p using naming service */
|
||||
Destination getDestination(String ip) {
|
||||
if (ip == null) return null;
|
||||
if (ip.endsWith(".i2p")) {
|
||||
@ -267,7 +327,40 @@ public class I2PSnarkUtil {
|
||||
return rv;
|
||||
}
|
||||
|
||||
public String getOpenTrackerString() {
|
||||
String rv = (String) _opts.get(PROP_OPENTRACKERS);
|
||||
if (rv == null)
|
||||
return DEFAULT_OPENTRACKERS;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** comma delimited list open trackers to use as backups */
|
||||
/** sorted map of name to announceURL=baseURL */
|
||||
public List getOpenTrackers() {
|
||||
if (!shouldUseOpenTrackers())
|
||||
return null;
|
||||
List rv = new ArrayList(1);
|
||||
String trackers = getOpenTrackerString();
|
||||
StringTokenizer tok = new StringTokenizer(trackers, ", ");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
|
||||
if (rv.size() <= 0)
|
||||
return null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean shouldUseOpenTrackers() {
|
||||
String rv = (String) _opts.get(PROP_USE_OPENTRACKERS);
|
||||
if (rv == null)
|
||||
return DEFAULT_USE_OPENTRACKERS;
|
||||
return Boolean.valueOf(rv).booleanValue();
|
||||
}
|
||||
|
||||
/** hook between snark's logger and an i2p log */
|
||||
void debug(String msg, int snarkDebugLevel) {
|
||||
debug(msg, snarkDebugLevel, null);
|
||||
}
|
||||
void debug(String msg, int snarkDebugLevel, Throwable t) {
|
||||
if (t instanceof OutOfMemoryError) {
|
||||
try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
|
@ -173,7 +173,7 @@ public class Peer implements Comparable
|
||||
* If the given BitField is non-null it is send to the peer as first
|
||||
* message.
|
||||
*/
|
||||
public void runConnection(PeerListener listener, BitField bitfield)
|
||||
public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield)
|
||||
{
|
||||
if (state != null)
|
||||
throw new IllegalStateException("Peer already started");
|
||||
@ -184,7 +184,7 @@ public class Peer implements Comparable
|
||||
// Do we need to handshake?
|
||||
if (din == null)
|
||||
{
|
||||
sock = I2PSnarkUtil.instance().connect(peerID);
|
||||
sock = util.connect(peerID);
|
||||
_log.debug("Connected to " + peerID + ": " + sock);
|
||||
if ((sock == null) || (sock.isClosed())) {
|
||||
throw new IOException("Unable to reach " + peerID);
|
||||
|
@ -32,12 +32,14 @@ import java.util.TimerTask;
|
||||
*/
|
||||
class PeerCheckerTask extends TimerTask
|
||||
{
|
||||
private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
|
||||
private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
|
||||
|
||||
private final PeerCoordinator coordinator;
|
||||
public I2PSnarkUtil _util;
|
||||
|
||||
PeerCheckerTask(PeerCoordinator coordinator)
|
||||
PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
|
||||
{
|
||||
_util = util;
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
@ -102,8 +104,8 @@ class PeerCheckerTask extends TimerTask
|
||||
peer.setRateHistory(upload, download);
|
||||
peer.resetCounters();
|
||||
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
_util.debug(peer + ":", Snark.DEBUG);
|
||||
_util.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
@ -129,7 +131,7 @@ class PeerCheckerTask extends TimerTask
|
||||
// Check if it still wants pieces from us.
|
||||
if (!peer.isInterested())
|
||||
{
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
_util.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
@ -141,7 +143,7 @@ class PeerCheckerTask extends TimerTask
|
||||
}
|
||||
else if (overBWLimitChoke)
|
||||
{
|
||||
Snark.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
|
||||
_util.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
@ -155,7 +157,7 @@ class PeerCheckerTask extends TimerTask
|
||||
else if (peer.isInteresting() && peer.isChoked())
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
_util.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
@ -168,7 +170,7 @@ class PeerCheckerTask extends TimerTask
|
||||
else if (!peer.isInteresting() && !coordinator.completed())
|
||||
{
|
||||
// If they aren't interesting make someone else a downloader
|
||||
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
|
||||
_util.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
@ -183,7 +185,7 @@ class PeerCheckerTask extends TimerTask
|
||||
&& download == 0)
|
||||
{
|
||||
// We are downloading but didn't receive anything...
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
_util.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
@ -222,7 +224,7 @@ class PeerCheckerTask extends TimerTask
|
||||
|| uploaders > uploadLimit)
|
||||
&& worstDownloader != null)
|
||||
{
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
_util.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
|
||||
worstDownloader.setChoking(true);
|
||||
@ -247,6 +249,10 @@ class PeerCheckerTask extends TimerTask
|
||||
// store the rates
|
||||
coordinator.setRateHistory(uploaded, downloaded);
|
||||
|
||||
// close out unused files, but we don't need to do it every time
|
||||
if (random.nextInt(4) == 0)
|
||||
coordinator.getStorage().cleanRAFs();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,9 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
@ -58,7 +59,7 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
thread = new I2PThread(this, "Snark sender " + _id + ": " + peer);
|
||||
thread = new I2PAppThread(this, "Snark sender " + _id + ": " + peer);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@ -215,7 +216,7 @@ class PeerConnectionOut implements Runnable
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
if (m.type == Message.PIECE)
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
|
@ -29,7 +29,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -44,7 +44,6 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 40*1000; // 40 seconds
|
||||
final static int MAX_CONNECTIONS = 16;
|
||||
final static int MAX_UPLOADERS = 6;
|
||||
|
||||
// Approximation of the number of current uploaders.
|
||||
@ -77,13 +76,15 @@ public class PeerCoordinator implements PeerListener
|
||||
private boolean halted = false;
|
||||
|
||||
private final CoordinatorListener listener;
|
||||
public I2PSnarkUtil _util;
|
||||
|
||||
public String trackerProblems = null;
|
||||
public int trackerSeenPeers = 0;
|
||||
|
||||
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
|
||||
public PeerCoordinator(I2PSnarkUtil util, byte[] id, MetaInfo metainfo, Storage storage,
|
||||
CoordinatorListener listener, Snark torrent)
|
||||
{
|
||||
_util = util;
|
||||
this.id = id;
|
||||
this.metainfo = metainfo;
|
||||
this.storage = storage;
|
||||
@ -96,7 +97,7 @@ public class PeerCoordinator implements PeerListener
|
||||
// Randomize the first start time so multiple tasks are spread out,
|
||||
// this will help the behavior with global limits
|
||||
Random r = new Random();
|
||||
timer.schedule(new PeerCheckerTask(this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
}
|
||||
|
||||
// only called externally from Storage after the double-check fails
|
||||
@ -235,7 +236,7 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return !halted && peers.size() < MAX_CONNECTIONS;
|
||||
return !halted && peers.size() < _util.getMaxConnections();
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +294,7 @@ public class PeerCoordinator implements PeerListener
|
||||
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||
}
|
||||
// This is already checked in addPeer() but we could have gone over the limit since then
|
||||
else if (peers.size() >= MAX_CONNECTIONS)
|
||||
else if (peers.size() >= _util.getMaxConnections())
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already at MAX_CONNECTIONS in connected() with peer: " + peer);
|
||||
@ -349,7 +350,7 @@ public class PeerCoordinator implements PeerListener
|
||||
peersize = peers.size();
|
||||
// This isn't a strict limit, as we may have several pending connections;
|
||||
// thus there is an additional check in connected()
|
||||
need_more = (!peer.isConnected()) && peersize < MAX_CONNECTIONS;
|
||||
need_more = (!peer.isConnected()) && peersize < _util.getMaxConnections();
|
||||
// Check if we already have this peer before we build the connection
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
|
||||
@ -366,18 +367,18 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
peer.runConnection(listener, bitfield);
|
||||
peer.runConnection(_util, listener, bitfield);
|
||||
}
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new I2PThread(r, threadName).start();
|
||||
new I2PAppThread(r, threadName).start();
|
||||
return true;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (peer.isConnected())
|
||||
_log.info("Add peer already connected: " + peer);
|
||||
else
|
||||
_log.info("Connections: " + peersize + "/" + MAX_CONNECTIONS
|
||||
_log.info("Connections: " + peersize + "/" + _util.getMaxConnections()
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
return false;
|
||||
@ -846,7 +847,7 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public int allowedUploaders()
|
||||
{
|
||||
if (Snark.overUploadLimit(uploaders)) {
|
||||
if (listener != null && listener.overUploadLimit(uploaders)) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Over limit, uploaders was: " + uploaders);
|
||||
return uploaders - 1;
|
||||
@ -858,12 +859,16 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
public boolean overUpBWLimit()
|
||||
{
|
||||
return Snark.overUpBWLimit();
|
||||
if (listener != null)
|
||||
return listener.overUpBWLimit();
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean overUpBWLimit(long total)
|
||||
{
|
||||
return Snark.overUpBWLimit(total * 1000 / CHECK_PERIOD);
|
||||
if (listener != null)
|
||||
return listener.overUpBWLimit(total * 1000 / CHECK_PERIOD);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,9 @@ import java.util.Set;
|
||||
* from it there too)
|
||||
*/
|
||||
public class PeerCoordinatorSet {
|
||||
private static final PeerCoordinatorSet _instance = new PeerCoordinatorSet();
|
||||
public static final PeerCoordinatorSet instance() { return _instance; }
|
||||
private Set _coordinators;
|
||||
|
||||
private PeerCoordinatorSet() {
|
||||
public PeerCoordinatorSet() {
|
||||
_coordinators = new HashSet();
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ public class PeerID implements Comparable
|
||||
bevalue = (BEValue)m.get("ip");
|
||||
if (bevalue == null)
|
||||
throw new InvalidBEncodingException("ip missing");
|
||||
address = I2PSnarkUtil.instance().getDestination(bevalue.getString());
|
||||
address = I2PSnarkUtil.getDestinationFromBase64(bevalue.getString());
|
||||
if (address == null)
|
||||
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
|
||||
|
||||
|
@ -60,7 +60,7 @@ class PeerState
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 2; // this is for outbound requests
|
||||
private final static int MAX_PIPELINE = 3; // this is for outbound requests
|
||||
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
|
||||
public final static int PARTSIZE = 32*1024; // Snark was 16K, i2p-bt uses 64KB
|
||||
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
|
||||
|
@ -28,12 +28,15 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
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;
|
||||
@ -102,7 +105,7 @@ public class Snark
|
||||
public void outOfMemory(OutOfMemoryError err) {
|
||||
try {
|
||||
err.printStackTrace();
|
||||
I2PSnarkUtil.instance().debug("OOM in the snark", Snark.ERROR, err);
|
||||
System.out.println("OOM in the snark" + err);
|
||||
} catch (Throwable t) {
|
||||
System.out.println("OOM in the OOM");
|
||||
}
|
||||
@ -225,15 +228,16 @@ public class Snark
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
debug("ERROR while reading stdin: " + ioe, ERROR);
|
||||
System.out.println("ERROR while reading stdin: " + ioe);
|
||||
}
|
||||
|
||||
// Explicit shutdown.
|
||||
Runtime.getRuntime().removeShutdownHook(snarkhook);
|
||||
//Runtime.getRuntime().removeShutdownHook(snarkhook);
|
||||
snarkhook.start();
|
||||
}
|
||||
}
|
||||
|
||||
public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections";
|
||||
public String torrent;
|
||||
public MetaInfo meta;
|
||||
public Storage storage;
|
||||
@ -244,19 +248,59 @@ public class Snark
|
||||
public CompleteListener completeListener;
|
||||
public boolean stopped;
|
||||
byte[] id;
|
||||
public I2PSnarkUtil _util;
|
||||
private PeerCoordinatorSet _peerCoordinatorSet;
|
||||
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
/** from main() via parseArguments() single torrent */
|
||||
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
this(torrent, ip, user_port, slistener, clistener, true, ".");
|
||||
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
|
||||
}
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener, boolean start, String rootDir)
|
||||
|
||||
/** single torrent - via router */
|
||||
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);
|
||||
int port = 0;
|
||||
String s = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_PORT);
|
||||
if (s != null) {
|
||||
try {
|
||||
port = Integer.parseInt(s);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_util.setI2CPConfig(host, port, opts);
|
||||
s = opts.getProperty(SnarkManager.PROP_UPBW_MAX);
|
||||
if (s != null) {
|
||||
try {
|
||||
int v = Integer.parseInt(s);
|
||||
_util.setMaxUpBW(v);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
s = opts.getProperty(PROP_MAX_CONNECTIONS);
|
||||
if (s != null) {
|
||||
try {
|
||||
int v = Integer.parseInt(s);
|
||||
_util.setMaxConnections(v);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
if (start)
|
||||
this.startTorrent();
|
||||
}
|
||||
|
||||
/** multitorrent */
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
|
||||
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
|
||||
{
|
||||
if (slistener == null)
|
||||
slistener = this;
|
||||
|
||||
//if (clistener == null)
|
||||
// clistener = this;
|
||||
completeListener = complistener;
|
||||
_util = util;
|
||||
_peerCoordinatorSet = peerCoordinatorSet;
|
||||
acceptor = connectionAcceptor;
|
||||
|
||||
this.torrent = torrent;
|
||||
this.rootDataDir = rootDir;
|
||||
@ -289,7 +333,7 @@ public class Snark
|
||||
while (i < 20)
|
||||
id[i++] = (byte)random.nextInt(256);
|
||||
|
||||
Snark.debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||
debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||
|
||||
int port;
|
||||
IOException lastException = null;
|
||||
@ -298,9 +342,9 @@ public class Snark
|
||||
* If we are starting,
|
||||
* startTorrent() will force a connect.
|
||||
*
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
boolean ok = util.connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
I2PServerSocket serversocket = util.getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else {
|
||||
@ -321,7 +365,7 @@ public class Snark
|
||||
else
|
||||
{
|
||||
activity = "Getting torrent";
|
||||
File torrentFile = I2PSnarkUtil.instance().get(torrent, 3);
|
||||
File torrentFile = _util.get(torrent, 3);
|
||||
if (torrentFile == null) {
|
||||
fatal("Unable to fetch " + torrent);
|
||||
if (false) return; // never reached - fatal(..) throws
|
||||
@ -345,7 +389,7 @@ public class Snark
|
||||
/*
|
||||
{
|
||||
// Try to create a new metainfo file
|
||||
Snark.debug
|
||||
debug
|
||||
("Trying to create metainfo torrent for '" + torrent + "'",
|
||||
NOTICE);
|
||||
try
|
||||
@ -378,8 +422,14 @@ public class Snark
|
||||
try
|
||||
{
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(meta, slistener);
|
||||
storage.check(rootDataDir);
|
||||
storage = new Storage(_util, meta, slistener);
|
||||
if (completeListener != null) {
|
||||
storage.check(rootDataDir,
|
||||
completeListener.getSavedTorrentTime(this),
|
||||
completeListener.getSavedTorrentBitField(this));
|
||||
} else {
|
||||
storage.check(rootDataDir);
|
||||
}
|
||||
// have to figure out when to reopen
|
||||
// if (!start)
|
||||
// storage.close();
|
||||
@ -413,10 +463,10 @@ public class Snark
|
||||
* Start up contacting peers and querying the tracker
|
||||
*/
|
||||
public void startTorrent() {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
if (coordinator == null) {
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
I2PServerSocket serversocket = _util.getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else {
|
||||
@ -425,12 +475,20 @@ public class Snark
|
||||
}
|
||||
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, this, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
coordinator = new PeerCoordinator(_util, id, meta, storage, this, this);
|
||||
if (_peerCoordinatorSet != null) {
|
||||
// multitorrent
|
||||
_peerCoordinatorSet.add(coordinator);
|
||||
if (acceptor != null) {
|
||||
acceptor.startAccepting(_peerCoordinatorSet, serversocket);
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
} else {
|
||||
// single torrent
|
||||
acceptor = new ConnectionAcceptor(_util, serversocket, new PeerAcceptor(coordinator));
|
||||
}
|
||||
trackerclient = new TrackerClient(_util, meta, coordinator);
|
||||
}
|
||||
|
||||
stopped = false;
|
||||
@ -438,11 +496,12 @@ public class Snark
|
||||
if (coordinator.halted()) {
|
||||
// ok, we have already started and stopped, but the coordinator seems a bit annoying to
|
||||
// restart safely, so lets build a new one to replace the old
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.remove(coordinator);
|
||||
PeerCoordinator newCoord = new PeerCoordinator(coordinator.getID(), coordinator.getMetaInfo(),
|
||||
if (_peerCoordinatorSet != null)
|
||||
_peerCoordinatorSet.remove(coordinator);
|
||||
PeerCoordinator newCoord = new PeerCoordinator(_util, coordinator.getID(), coordinator.getMetaInfo(),
|
||||
coordinator.getStorage(), coordinator.getListener(), this);
|
||||
set.add(newCoord);
|
||||
if (_peerCoordinatorSet != null)
|
||||
_peerCoordinatorSet.add(newCoord);
|
||||
coordinator = newCoord;
|
||||
coordinatorChanged = true;
|
||||
}
|
||||
@ -460,7 +519,7 @@ public class Snark
|
||||
}
|
||||
fatal("Could not reopen storage", ioe);
|
||||
}
|
||||
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
|
||||
TrackerClient newClient = new TrackerClient(_util, coordinator.getMetaInfo(), coordinator);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
trackerclient = newClient;
|
||||
@ -480,17 +539,20 @@ public class Snark
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (st != null) {
|
||||
if (storage.changed)
|
||||
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
|
||||
boolean changed = storage.changed;
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
}
|
||||
if (pc != null)
|
||||
PeerCoordinatorSet.instance().remove(pc);
|
||||
if (pc != null && _peerCoordinatorSet != null)
|
||||
_peerCoordinatorSet.remove(pc);
|
||||
if (_peerCoordinatorSet == null)
|
||||
_util.disconnect();
|
||||
}
|
||||
|
||||
static Snark parseArguments(String[] args)
|
||||
@ -512,7 +574,8 @@ public class Snark
|
||||
String ip = null;
|
||||
String torrent = null;
|
||||
|
||||
boolean configured = I2PSnarkUtil.instance().configured();
|
||||
I2PSnarkUtil util = new I2PSnarkUtil(I2PAppContext.getGlobalContext());
|
||||
boolean configured = util.configured();
|
||||
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
@ -562,7 +625,7 @@ public class Snark
|
||||
String proxyHost = args[i+1];
|
||||
String proxyPort = args[i+2];
|
||||
if (!configured)
|
||||
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
util.setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
i += 3;
|
||||
}
|
||||
else if (args[i].equals("--i2cp"))
|
||||
@ -584,7 +647,7 @@ public class Snark
|
||||
}
|
||||
}
|
||||
if (!configured)
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||
util.setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||
i += 3 + (opts != null ? 1 : 0);
|
||||
}
|
||||
else
|
||||
@ -601,7 +664,7 @@ public class Snark
|
||||
else
|
||||
usage("Need exactly one <url>, <file> or <dir>.");
|
||||
|
||||
return new Snark(torrent, ip, user_port, slistener, clistener);
|
||||
return new Snark(util, torrent, ip, user_port, slistener, clistener);
|
||||
}
|
||||
|
||||
private static void usage(String s)
|
||||
@ -668,7 +731,7 @@ public class Snark
|
||||
*/
|
||||
public void fatal(String s, Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, ERROR, t);
|
||||
_util.debug(s, ERROR, t);
|
||||
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
|
||||
//if (debug >= INFO && t != null)
|
||||
// t.printStackTrace();
|
||||
@ -679,13 +742,12 @@ public class Snark
|
||||
/**
|
||||
* Show debug info if debug is true.
|
||||
*/
|
||||
public static void debug(String s, int level)
|
||||
private void debug(String s, int level)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, level, null);
|
||||
//if (debug >= level)
|
||||
// System.out.println(s);
|
||||
_util.debug(s, level, null);
|
||||
}
|
||||
|
||||
/** coordinatorListener */
|
||||
public void peerChange(PeerCoordinator coordinator, Peer peer)
|
||||
{
|
||||
// System.out.println(peer.toString());
|
||||
@ -732,7 +794,7 @@ public class Snark
|
||||
checking = true;
|
||||
}
|
||||
if (!checking)
|
||||
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
|
||||
debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
|
||||
Snark.INFO);
|
||||
}
|
||||
|
||||
@ -743,11 +805,13 @@ public class Snark
|
||||
|
||||
allChecked = true;
|
||||
checking = false;
|
||||
if (storage.changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
}
|
||||
|
||||
public void storageCompleted(Storage storage)
|
||||
{
|
||||
Snark.debug("Completely received " + torrent, Snark.INFO);
|
||||
debug("Completely received " + torrent, Snark.INFO);
|
||||
//storage.close();
|
||||
//System.out.println("Completely received: " + torrent);
|
||||
if (completeListener != null)
|
||||
@ -768,44 +832,47 @@ public class Snark
|
||||
|
||||
public interface CompleteListener {
|
||||
public void torrentComplete(Snark snark);
|
||||
public void updateStatus(Snark snark);
|
||||
// not really listeners but the easiest way to get back to an optional SnarkManager
|
||||
public long getSavedTorrentTime(Snark snark);
|
||||
public BitField getSavedTorrentBitField(Snark snark);
|
||||
}
|
||||
|
||||
/** Maintain a configurable total uploader cap
|
||||
* coordinatorListener
|
||||
*/
|
||||
final static int MIN_TOTAL_UPLOADERS = 4;
|
||||
final static int MAX_TOTAL_UPLOADERS = 10;
|
||||
public static boolean overUploadLimit(int uploaders) {
|
||||
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
|
||||
if (coordinators == null || uploaders <= 0)
|
||||
public boolean overUploadLimit(int uploaders) {
|
||||
if (_peerCoordinatorSet == null || uploaders <= 0)
|
||||
return false;
|
||||
int totalUploaders = 0;
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
for (Iterator iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = (PeerCoordinator)iter.next();
|
||||
if (!c.halted())
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
int limit = I2PSnarkUtil.instance().getMaxUploaders();
|
||||
// Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
int limit = _util.getMaxUploaders();
|
||||
// debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
return totalUploaders > limit;
|
||||
}
|
||||
|
||||
public static boolean overUpBWLimit() {
|
||||
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
|
||||
if (coordinators == null)
|
||||
public boolean overUpBWLimit() {
|
||||
if (_peerCoordinatorSet == null)
|
||||
return false;
|
||||
long total = 0;
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
for (Iterator iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = (PeerCoordinator)iter.next();
|
||||
if (!c.halted())
|
||||
total += c.getCurrentUploadRate();
|
||||
}
|
||||
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
|
||||
Snark.debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
|
||||
long limit = 1024l * _util.getMaxUpBW();
|
||||
debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
|
||||
return total > limit;
|
||||
}
|
||||
|
||||
public static boolean overUpBWLimit(long total) {
|
||||
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
|
||||
public boolean overUpBWLimit(long total) {
|
||||
long limit = 1024l * _util.getMaxUpBW();
|
||||
return total > limit;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -32,11 +32,14 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private Map _snarks;
|
||||
private Object _addSnarkLock;
|
||||
private String _configFile;
|
||||
private String _configFile = "i2psnark.config";
|
||||
private Properties _config;
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private List _messages;
|
||||
private I2PSnarkUtil _util;
|
||||
private PeerCoordinatorSet _peerCoordinatorSet;
|
||||
private ConnectionAcceptor _connectionAcceptor;
|
||||
|
||||
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
|
||||
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
|
||||
@ -51,10 +54,6 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
|
||||
public static final String DEFAULT_USE_OPENTRACKERS = "true";
|
||||
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
|
||||
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
|
||||
public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
|
||||
public static final String DEFAULT_LINK_PREFIX = "file:///";
|
||||
|
||||
@ -67,16 +66,27 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
loadConfig("i2psnark.config");
|
||||
_util = new I2PSnarkUtil(_context);
|
||||
loadConfig(null);
|
||||
}
|
||||
|
||||
/** Caller _must_ call loadConfig(file) before this if setting new values
|
||||
* for i2cp host/port or i2psnark.dir
|
||||
*/
|
||||
public void start() {
|
||||
_peerCoordinatorSet = new PeerCoordinatorSet();
|
||||
_connectionAcceptor = new ConnectionAcceptor(_util);
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
|
||||
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 */
|
||||
public I2PSnarkUtil util() { return _util; }
|
||||
|
||||
private static final int MAX_MESSAGES = 5;
|
||||
public void addMessage(String message) {
|
||||
synchronized (_messages) {
|
||||
@ -98,9 +108,6 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public boolean shouldAutoStart() {
|
||||
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
|
||||
}
|
||||
public boolean shouldUseOpenTrackers() {
|
||||
return Boolean.valueOf(_config.getProperty(PROP_USE_OPENTRACKERS, DEFAULT_USE_OPENTRACKERS)).booleanValue();
|
||||
}
|
||||
public String linkPrefix() {
|
||||
return _config.getProperty(PROP_LINK_PREFIX, DEFAULT_LINK_PREFIX + getDataDir().getAbsolutePath() + File.separatorChar);
|
||||
}
|
||||
@ -112,17 +119,20 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
return new File(dir);
|
||||
}
|
||||
|
||||
/** null to set initial defaults */
|
||||
public void loadConfig(String filename) {
|
||||
_configFile = filename;
|
||||
if (_config == null)
|
||||
_config = new Properties();
|
||||
File cfg = new File(filename);
|
||||
if (cfg.exists()) {
|
||||
try {
|
||||
DataHelper.loadProps(_config, cfg);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading I2PSnark config '" + filename + "'", ioe);
|
||||
}
|
||||
if (filename != null) {
|
||||
_configFile = filename;
|
||||
File cfg = new File(filename);
|
||||
if (cfg.exists()) {
|
||||
try {
|
||||
DataHelper.loadProps(_config, cfg);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading I2PSnark config '" + filename + "'", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
// now add sane defaults
|
||||
if (!_config.containsKey(PROP_I2CP_HOST))
|
||||
@ -130,7 +140,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
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");
|
||||
_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");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
@ -169,16 +179,16 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
|
||||
_util.setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
|
||||
_log.debug("Configuring with I2CP options " + i2cpOpts);
|
||||
}
|
||||
//I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties());
|
||||
String eepHost = _config.getProperty(PROP_EEP_HOST);
|
||||
int eepPort = getInt(PROP_EEP_PORT, 4444);
|
||||
if (eepHost != null)
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
|
||||
I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
|
||||
I2PSnarkUtil.instance().setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
|
||||
_util.setProxy(eepHost, eepPort);
|
||||
_util.setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
|
||||
_util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
|
||||
getDataDir().mkdirs();
|
||||
}
|
||||
|
||||
@ -198,12 +208,12 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) {
|
||||
boolean changed = false;
|
||||
if (eepHost != null) {
|
||||
int port = I2PSnarkUtil.instance().getEepProxyPort();
|
||||
int port = _util.getEepProxyPort();
|
||||
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
|
||||
String host = I2PSnarkUtil.instance().getEepProxyHost();
|
||||
String host = _util.getEepProxyHost();
|
||||
if ( (eepHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, port);
|
||||
((!host.equals(eepHost) || (port != _util.getEepProxyPort()) )) ) {
|
||||
_util.setProxy(eepHost, port);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_EEP_HOST, eepHost);
|
||||
_config.setProperty(PROP_EEP_PORT, eepPort+"");
|
||||
@ -211,11 +221,11 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
if (upLimit != null) {
|
||||
int limit = I2PSnarkUtil.instance().getMaxUploaders();
|
||||
int limit = _util.getMaxUploaders();
|
||||
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != I2PSnarkUtil.instance().getMaxUploaders()) {
|
||||
if ( limit != _util.getMaxUploaders()) {
|
||||
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
|
||||
I2PSnarkUtil.instance().setMaxUploaders(limit);
|
||||
_util.setMaxUploaders(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
|
||||
addMessage("Total uploaders limit changed to " + limit);
|
||||
@ -225,11 +235,11 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
if (upBW != null) {
|
||||
int limit = I2PSnarkUtil.instance().getMaxUpBW();
|
||||
int limit = _util.getMaxUpBW();
|
||||
try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != I2PSnarkUtil.instance().getMaxUpBW()) {
|
||||
if ( limit != _util.getMaxUpBW()) {
|
||||
if ( limit >= MIN_UP_BW ) {
|
||||
I2PSnarkUtil.instance().setMaxUpBW(limit);
|
||||
_util.setMaxUpBW(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPBW_MAX, "" + limit);
|
||||
addMessage("Up BW limit changed to " + limit + "KBps");
|
||||
@ -239,8 +249,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
|
||||
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
|
||||
int oldI2CPPort = _util.getI2CPPort();
|
||||
String oldI2CPHost = _util.getI2CPHost();
|
||||
int port = oldI2CPPort;
|
||||
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
|
||||
String host = oldI2CPHost;
|
||||
@ -266,7 +276,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(i2cpHost) ||
|
||||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
|
||||
(port != _util.getI2CPPort()) ||
|
||||
(!oldOpts.equals(opts)))) ) {
|
||||
boolean snarksActive = false;
|
||||
Set names = listTorrentFiles();
|
||||
@ -282,19 +292,19 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
|
||||
+ "] oldOpts [" + oldOpts + "]");
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().disconnect();
|
||||
if (_util.connected()) {
|
||||
_util.disconnect();
|
||||
addMessage("Disconnecting old I2CP destination");
|
||||
}
|
||||
Properties p = new Properties();
|
||||
p.putAll(opts);
|
||||
addMessage("I2CP settings changed to " + i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")");
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, port, p);
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
_util.setI2CPConfig(i2cpHost, port, p);
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) {
|
||||
addMessage("Unable to connect with the new settings, reverting to the old I2CP settings");
|
||||
I2PSnarkUtil.instance().setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
|
||||
ok = I2PSnarkUtil.instance().connect();
|
||||
_util.setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
|
||||
ok = _util.connect();
|
||||
if (!ok)
|
||||
addMessage("Unable to reconnect with the old settings!");
|
||||
} else {
|
||||
@ -322,14 +332,14 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
addMessage("Adjusted autostart to " + autoStart);
|
||||
changed = true;
|
||||
}
|
||||
if (shouldUseOpenTrackers() != useOpenTrackers) {
|
||||
_config.setProperty(PROP_USE_OPENTRACKERS, useOpenTrackers + "");
|
||||
if (_util.shouldUseOpenTrackers() != useOpenTrackers) {
|
||||
_config.setProperty(I2PSnarkUtil.PROP_USE_OPENTRACKERS, useOpenTrackers + "");
|
||||
addMessage((useOpenTrackers ? "En" : "Dis") + "abled open trackers - torrent restart required to take effect");
|
||||
changed = true;
|
||||
}
|
||||
if (openTrackers != null) {
|
||||
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(getOpenTrackerString())) {
|
||||
_config.setProperty(PROP_OPENTRACKERS, openTrackers.trim());
|
||||
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(_util.getOpenTrackerString())) {
|
||||
_config.setProperty(I2PSnarkUtil.PROP_OPENTRACKERS, openTrackers.trim());
|
||||
addMessage("Open Tracker list changed - torrent restart required to take effect");
|
||||
changed = true;
|
||||
}
|
||||
@ -354,7 +364,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public Properties getConfig() { return _config; }
|
||||
|
||||
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
|
||||
private static final int MAX_FILES_PER_TORRENT = 128;
|
||||
private static final int MAX_FILES_PER_TORRENT = 256;
|
||||
|
||||
/** set of filenames that we are dealing with */
|
||||
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
|
||||
@ -364,9 +374,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
public void addTorrent(String filename, boolean dontAutoStart) {
|
||||
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
|
||||
if ((!dontAutoStart) && !_util.connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) {
|
||||
addMessage("Error connecting to I2P - check your I2CP settings");
|
||||
return;
|
||||
@ -407,7 +417,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
addMessage(rejectMessage);
|
||||
return;
|
||||
} else {
|
||||
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
|
||||
torrent = new Snark(_util, filename, null, -1, null, null, this,
|
||||
_peerCoordinatorSet, _connectionAcceptor,
|
||||
false, dataDir.getPath());
|
||||
torrent.completeListener = this;
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
@ -438,7 +450,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
/**
|
||||
* Get the timestamp for a torrent from the config file
|
||||
*/
|
||||
public long getSavedTorrentTime(MetaInfo metainfo) {
|
||||
public long getSavedTorrentTime(Snark snark) {
|
||||
MetaInfo metainfo = snark.meta;
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
@ -457,7 +470,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
* Get the saved bitfield for a torrent from the config file.
|
||||
* Convert "." to a full bitfield.
|
||||
*/
|
||||
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
|
||||
public BitField getSavedTorrentBitField(Snark snark) {
|
||||
MetaInfo metainfo = snark.meta;
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
@ -524,7 +538,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) ) {
|
||||
@ -574,7 +588,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (remaining == 0) {
|
||||
// should we disconnect/reconnect here (taking care to deal with the other thread's
|
||||
// I2PServerSocket.accept() call properly?)
|
||||
////I2PSnarkUtil.instance().
|
||||
////_util.
|
||||
}
|
||||
if (!wasStopped)
|
||||
addMessage("Torrent stopped: '" + sfile.getName() + "'");
|
||||
@ -618,11 +632,17 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
/** two listeners */
|
||||
public void torrentComplete(Snark snark) {
|
||||
File f = new File(snark.torrent);
|
||||
long len = snark.meta.getTotalLength();
|
||||
addMessage("Download complete of " + f.getName()
|
||||
+ (len < 5*1024*1024 ? " (size: " + (len/1024) + "KB)" : " (size: " + (len/(1024*1024l)) + "MB)"));
|
||||
updateStatus(snark);
|
||||
}
|
||||
|
||||
public void updateStatus(Snark snark) {
|
||||
saveTorrentStatus(snark.meta, snark.storage.getBitField());
|
||||
}
|
||||
|
||||
private void monitorTorrents(File dir) {
|
||||
@ -644,7 +664,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
|
||||
if (shouldAutoStart() && !_util.connect())
|
||||
addMessage("Unable to connect to I2P");
|
||||
addTorrent((String)foundNames.get(i), !shouldAutoStart());
|
||||
}
|
||||
@ -672,6 +692,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
, "welterde", "http://BGKmlDOoH3RzFbPRfRpZV2FjpVj8~3moFftw5-dZfDf2070TOe8Tf2~DAVeaM6ZRLdmFEt~9wyFL8YMLMoLoiwGEH6IGW6rc45tstN68KsBDWZqkTohV1q9XFgK9JnCwE~Oi89xLBHsLMTHOabowWM6dkC8nI6QqJC2JODqLPIRfOVrDdkjLwtCrsckzLybNdFmgfoqF05UITDyczPsFVaHtpF1sRggOVmdvCM66otyonlzNcJbn59PA-R808vUrCPMGU~O9Wys0i-NoqtIbtWfOKnjCRFMNw5ex4n9m5Sxm9e20UkpKG6qzEuvKZWi8vTLe1NW~CBrj~vG7I3Ok4wybUFflBFOaBabxYJLlx4xTE1zJIVxlsekmAjckB4v-cQwulFeikR4LxPQ6mCQknW2HZ4JQIq6hL9AMabxjOlYnzh7kjOfRGkck8YgeozcyTvcDUcUsOuSTk06L4kdrv8h2Cozjbloi5zl6KTbj5ZTciKCxi73Pn9grICn-HQqEAAAA.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
|
||||
// , "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/"
|
||||
// , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
|
||||
, "crstrack", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/"
|
||||
};
|
||||
|
||||
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
|
||||
@ -706,26 +727,6 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
return trackerMap;
|
||||
}
|
||||
|
||||
public String getOpenTrackerString() {
|
||||
return _config.getProperty(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS);
|
||||
}
|
||||
|
||||
/** comma delimited list open trackers to use as backups */
|
||||
/** sorted map of name to announceURL=baseURL */
|
||||
public List getOpenTrackers() {
|
||||
if (!shouldUseOpenTrackers())
|
||||
return null;
|
||||
List rv = new ArrayList(1);
|
||||
String trackers = getOpenTrackerString();
|
||||
StringTokenizer tok = new StringTokenizer(trackers, ", ");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
|
||||
if (rv.size() <= 0)
|
||||
return null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static class TorrentFilenameFilter implements FilenameFilter {
|
||||
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
|
||||
public static TorrentFilenameFilter instance() { return _filter; }
|
||||
@ -734,7 +735,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
public class SnarkManagerShutdown extends I2PThread {
|
||||
public class SnarkManagerShutdown extends I2PAppThread {
|
||||
public void run() {
|
||||
Set names = listTorrentFiles();
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
|
@ -22,12 +22,12 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Makes sure everything ends correctly when shutting down.
|
||||
*/
|
||||
public class SnarkShutdown extends I2PThread
|
||||
public class SnarkShutdown extends I2PAppThread
|
||||
{
|
||||
private final Storage storage;
|
||||
private final PeerCoordinator coordinator;
|
||||
@ -51,21 +51,21 @@ public class SnarkShutdown extends I2PThread
|
||||
|
||||
public void run()
|
||||
{
|
||||
Snark.debug("Shutting down...", Snark.NOTICE);
|
||||
//Snark.debug("Shutting down...", Snark.NOTICE);
|
||||
|
||||
Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
|
||||
//Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
|
||||
if (acceptor != null)
|
||||
acceptor.halt();
|
||||
|
||||
Snark.debug("Halting TrackerClient...", Snark.INFO);
|
||||
//Snark.debug("Halting TrackerClient...", Snark.INFO);
|
||||
if (trackerclient != null)
|
||||
trackerclient.halt();
|
||||
|
||||
Snark.debug("Halting PeerCoordinator...", Snark.INFO);
|
||||
//Snark.debug("Halting PeerCoordinator...", Snark.INFO);
|
||||
if (coordinator != null)
|
||||
coordinator.halt();
|
||||
|
||||
Snark.debug("Closing Storage...", Snark.INFO);
|
||||
//Snark.debug("Closing Storage...", Snark.INFO);
|
||||
if (storage != null)
|
||||
{
|
||||
try
|
||||
@ -74,7 +74,7 @@ public class SnarkShutdown extends I2PThread
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
|
||||
//I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
|
||||
throw new RuntimeException("b0rking");
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,7 @@ public class SnarkShutdown extends I2PThread
|
||||
// XXX - Should actually wait till done...
|
||||
try
|
||||
{
|
||||
Snark.debug("Waiting 5 seconds...", Snark.INFO);
|
||||
//Snark.debug("Waiting 5 seconds...", Snark.INFO);
|
||||
Thread.sleep(5*1000);
|
||||
}
|
||||
catch (InterruptedException ie) { /* ignored */ }
|
||||
|
@ -39,11 +39,16 @@ public class Storage
|
||||
private long[] lengths;
|
||||
private RandomAccessFile[] rafs;
|
||||
private String[] names;
|
||||
private Object[] RAFlock; // lock on RAF access
|
||||
private long[] RAFtime; // when was RAF last accessed, or 0 if closed
|
||||
private File[] RAFfile; // File to make it easier to reopen
|
||||
|
||||
private final StorageListener listener;
|
||||
private I2PSnarkUtil _util;
|
||||
|
||||
private BitField bitfield; // BitField to represent the pieces
|
||||
private int needed; // Number of pieces needed
|
||||
private boolean _probablyComplete; // use this to decide whether to open files RO
|
||||
|
||||
// XXX - Not always set correctly
|
||||
int piece_size;
|
||||
@ -62,12 +67,14 @@ public class Storage
|
||||
*
|
||||
* @exception IOException when creating and/or checking files fails.
|
||||
*/
|
||||
public Storage(MetaInfo metainfo, StorageListener listener)
|
||||
public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
_util = util;
|
||||
this.metainfo = metainfo;
|
||||
this.listener = listener;
|
||||
needed = metainfo.getPieces();
|
||||
_probablyComplete = false;
|
||||
bitfield = new BitField(needed);
|
||||
}
|
||||
|
||||
@ -76,9 +83,10 @@ public class Storage
|
||||
* with an appropriate MetaInfo file as can be announced on the
|
||||
* given announce String location.
|
||||
*/
|
||||
public Storage(File baseFile, String announce, StorageListener listener)
|
||||
public Storage(I2PSnarkUtil util, File baseFile, String announce, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
_util = util;
|
||||
this.listener = listener;
|
||||
// Create names, rafs and lengths arrays.
|
||||
getFiles(baseFile);
|
||||
@ -119,7 +127,6 @@ public class Storage
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
String name = baseFile.getName();
|
||||
if (files.size() == 1) // FIXME: ...and if base file not a directory or should this be the only check?
|
||||
// this makes a bad metainfo if the directory has only one file in it
|
||||
{
|
||||
@ -133,7 +140,8 @@ public class Storage
|
||||
|
||||
}
|
||||
|
||||
// Creates piece hases for a new storage.
|
||||
// Creates piece hashes for a new storage.
|
||||
// This does NOT create the files, just the hashes
|
||||
public void create() throws IOException
|
||||
{
|
||||
// if (true) {
|
||||
@ -197,14 +205,8 @@ public class Storage
|
||||
piece_hashes[20 * i + j] = hash[j];
|
||||
|
||||
bitfield.set(i);
|
||||
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, i, true);
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.storageAllChecked(this);
|
||||
|
||||
// Reannounce to force recalculating the info_hash.
|
||||
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||
}
|
||||
@ -218,6 +220,9 @@ public class Storage
|
||||
names = new String[size];
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
RAFlock = new Object[size];
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
@ -228,12 +233,13 @@ public class Storage
|
||||
if (base.isDirectory() && names[i].startsWith(base.getPath()))
|
||||
names[i] = names[i].substring(base.getPath().length() + 1);
|
||||
lengths[i] = f.length();
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
RAFlock[i] = new Object();
|
||||
RAFfile[i] = f;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFiles(List l, File f)
|
||||
private void addFiles(List l, File f)
|
||||
{
|
||||
if (!f.isDirectory())
|
||||
l.add(f);
|
||||
@ -242,7 +248,7 @@ public class Storage
|
||||
File[] files = f.listFiles();
|
||||
if (files == null)
|
||||
{
|
||||
Snark.debug("WARNING: Skipping '" + f
|
||||
_util.debug("WARNING: Skipping '" + f
|
||||
+ "' not a normal file.", Snark.WARNING);
|
||||
return;
|
||||
}
|
||||
@ -288,40 +294,44 @@ public class Storage
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
*/
|
||||
public void check(String rootDir) throws IOException
|
||||
{
|
||||
check(rootDir, 0, null);
|
||||
}
|
||||
|
||||
/** use a saved bitfield and timestamp from a config file */
|
||||
public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
// look for saved bitfield and timestamp in the config file
|
||||
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
|
||||
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
|
||||
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// Create base as file.
|
||||
Snark.debug("Creating/Checking file: " + base, Snark.NOTICE);
|
||||
_util.debug("Creating/Checking file: " + base, Snark.NOTICE);
|
||||
if (!base.createNewFile() && !base.exists())
|
||||
throw new IOException("Could not create file " + base);
|
||||
|
||||
lengths = new long[1];
|
||||
rafs = new RandomAccessFile[1];
|
||||
names = new String[1];
|
||||
RAFlock = new Object[1];
|
||||
RAFtime = new long[1];
|
||||
RAFfile = new File[1];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
RAFlock[0] = new Object();
|
||||
RAFfile[0] = base;
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
if (base.exists() && ((useSavedBitField && savedBitField.complete()) || !base.canWrite()))
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
names[0] = base.getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create base as dir.
|
||||
Snark.debug("Creating/Checking directory: " + base, Snark.NOTICE);
|
||||
_util.debug("Creating/Checking directory: " + base, Snark.NOTICE);
|
||||
if (!base.mkdir() && !base.isDirectory())
|
||||
throw new IOException("Could not create directory " + base);
|
||||
|
||||
@ -331,20 +341,21 @@ public class Storage
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
names = new String[size];
|
||||
RAFlock = new Object[size];
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||
RAFlock[i] = new Object();
|
||||
RAFfile[i] = f;
|
||||
total += lengths[i];
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
if (f.exists() && ((useSavedBitField && savedBitField.complete()) || !f.canWrite()))
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
names[i] = f.getName();
|
||||
}
|
||||
|
||||
@ -357,11 +368,17 @@ public class Storage
|
||||
if (useSavedBitField) {
|
||||
bitfield = savedBitField;
|
||||
needed = metainfo.getPieces() - bitfield.count();
|
||||
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
|
||||
_probablyComplete = complete();
|
||||
_util.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
|
||||
} else {
|
||||
// the following sets the needed variable
|
||||
changed = true;
|
||||
checkCreateFiles();
|
||||
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
|
||||
}
|
||||
if (complete())
|
||||
_util.debug("Torrent is complete", Snark.NOTICE);
|
||||
else
|
||||
_util.debug("Still need " + needed + " out of " + metainfo.getPieces() + " pieces", Snark.NOTICE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,19 +393,14 @@ public class Storage
|
||||
if (files == null)
|
||||
{
|
||||
// Reopen base as file.
|
||||
Snark.debug("Reopening file: " + base, Snark.NOTICE);
|
||||
_util.debug("Reopening file: " + base, Snark.NOTICE);
|
||||
if (!base.exists())
|
||||
throw new IOException("Could not reopen file " + base);
|
||||
|
||||
if (complete() || !base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reopen base as dir.
|
||||
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
|
||||
_util.debug("Reopening directory: " + base, Snark.NOTICE);
|
||||
if (!base.isDirectory())
|
||||
throw new IOException("Could not reopen directory " + base);
|
||||
|
||||
@ -398,10 +410,6 @@ public class Storage
|
||||
File f = getFileFromNames(base, (List)files.get(i));
|
||||
if (!f.exists())
|
||||
throw new IOException("Could not reopen file " + f);
|
||||
if (complete() || !f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
}
|
||||
|
||||
}
|
||||
@ -453,27 +461,43 @@ public class Storage
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called at the beginning, and at presumed completion,
|
||||
* so we have to be careful about locking.
|
||||
*/
|
||||
private void checkCreateFiles() throws IOException
|
||||
{
|
||||
// Whether we are resuming or not,
|
||||
// if any of the files already exists we assume we are resuming.
|
||||
boolean resume = false;
|
||||
|
||||
_probablyComplete = true;
|
||||
needed = metainfo.getPieces();
|
||||
|
||||
// Make sure all files are available and of correct length
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
long length = rafs[i].length();
|
||||
if(length == lengths[i])
|
||||
long length = RAFfile[i].length();
|
||||
if(RAFfile[i].exists() && length == lengths[i])
|
||||
{
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, length);
|
||||
resume = true; // XXX Could dynamicly check
|
||||
}
|
||||
else if (length == 0)
|
||||
allocateFile(i);
|
||||
else {
|
||||
Snark.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
|
||||
rafs[i].setLength(lengths[i]);
|
||||
else if (length == 0) {
|
||||
changed = true;
|
||||
synchronized(RAFlock[i]) {
|
||||
allocateFile(i);
|
||||
}
|
||||
} else {
|
||||
_util.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
|
||||
changed = true;
|
||||
_probablyComplete = false; // to force RW
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
rafs[i].setLength(lengths[i]);
|
||||
}
|
||||
// will be closed below
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,6 +521,17 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
_probablyComplete = complete();
|
||||
// close all the files so we don't end up with a zillion open ones;
|
||||
// we will reopen as needed
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
synchronized(RAFlock[i]) {
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.storageAllChecked(this);
|
||||
if (needed <= 0)
|
||||
@ -506,6 +541,8 @@ public class Storage
|
||||
|
||||
private void allocateFile(int nr) throws IOException
|
||||
{
|
||||
// caller synchronized
|
||||
openRAF(nr, false); // RW
|
||||
// XXX - Is this the best way to make sure we have enough space for
|
||||
// the whole file?
|
||||
listener.storageCreateFile(this, names[nr], lengths[nr]);
|
||||
@ -520,6 +557,7 @@ public class Storage
|
||||
}
|
||||
int size = (int)(lengths[nr] - i*ZEROBLOCKSIZE);
|
||||
rafs[nr].write(zeros, 0, size);
|
||||
// caller will close rafs[nr]
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, size);
|
||||
}
|
||||
@ -535,12 +573,11 @@ public class Storage
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
try {
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].close();
|
||||
}
|
||||
synchronized(RAFlock[i]) {
|
||||
closeRAF(i);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
I2PSnarkUtil.instance().debug("Error closing " + rafs[i], Snark.ERROR, ioe);
|
||||
_util.debug("Error closing " + RAFfile[i], Snark.ERROR, ioe);
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
@ -561,7 +598,7 @@ public class Storage
|
||||
try {
|
||||
bs = new byte[len];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
|
||||
_util.debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
|
||||
return null;
|
||||
}
|
||||
getUncheckedPiece(piece, bs, off, len);
|
||||
@ -587,18 +624,10 @@ public class Storage
|
||||
if (!correctHash)
|
||||
return false;
|
||||
|
||||
boolean complete;
|
||||
synchronized(bitfield)
|
||||
{
|
||||
if (bitfield.get(piece))
|
||||
return true; // No need to store twice.
|
||||
else
|
||||
{
|
||||
bitfield.set(piece);
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
@ -618,8 +647,9 @@ public class Storage
|
||||
{
|
||||
int need = length - written;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(rafs[i])
|
||||
synchronized(RAFlock[i])
|
||||
{
|
||||
checkRAF(i);
|
||||
rafs[i].seek(start);
|
||||
rafs[i].write(bs, off + written, len);
|
||||
}
|
||||
@ -633,8 +663,21 @@ public class Storage
|
||||
}
|
||||
|
||||
changed = true;
|
||||
|
||||
// do this after the write, so we know it succeeded, and we don't set the
|
||||
// needed count to zero, which would cause checkRAF() to open the file readonly.
|
||||
boolean complete = false;
|
||||
synchronized(bitfield)
|
||||
{
|
||||
if (!bitfield.get(piece))
|
||||
{
|
||||
bitfield.set(piece);
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
// listener.storageCompleted(this);
|
||||
// do we also need to close all of the files and reopen
|
||||
// them readonly?
|
||||
|
||||
@ -649,11 +692,10 @@ public class Storage
|
||||
bitfield = new BitField(needed);
|
||||
checkCreateFiles();
|
||||
if (needed > 0) {
|
||||
listener.setWantedPieces(this);
|
||||
Snark.debug("WARNING: Not really done, missing " + needed
|
||||
if (listener != null)
|
||||
listener.setWantedPieces(this);
|
||||
_util.debug("WARNING: Not really done, missing " + needed
|
||||
+ " pieces", Snark.WARNING);
|
||||
} else {
|
||||
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,8 +730,9 @@ public class Storage
|
||||
{
|
||||
int need = length - read;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(rafs[i])
|
||||
synchronized(RAFlock[i])
|
||||
{
|
||||
checkRAF(i);
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, read, len);
|
||||
}
|
||||
@ -704,4 +747,59 @@ public class Storage
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close unused RAFs - call periodically
|
||||
*/
|
||||
private static final long RAFCloseDelay = 7*60*1000;
|
||||
public void cleanRAFs() {
|
||||
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
|
||||
for (int i = 0; i < RAFlock.length; i++) {
|
||||
synchronized(RAFlock[i]) {
|
||||
if (RAFtime[i] > 0 && RAFtime[i] < cutoff) {
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For each of the following,
|
||||
* caller must synchronize on RAFlock[i]
|
||||
* ... except at the beginning if you're careful
|
||||
*/
|
||||
|
||||
/**
|
||||
* This must be called before using the RAF to ensure it is open
|
||||
*/
|
||||
private void checkRAF(int i) throws IOException {
|
||||
if (RAFtime[i] > 0) {
|
||||
RAFtime[i] = System.currentTimeMillis();
|
||||
return;
|
||||
}
|
||||
openRAF(i);
|
||||
}
|
||||
|
||||
private void openRAF(int i) throws IOException {
|
||||
openRAF(i, _probablyComplete);
|
||||
}
|
||||
|
||||
private void openRAF(int i, boolean readonly) throws IOException {
|
||||
rafs[i] = new RandomAccessFile(RAFfile[i], (readonly || !RAFfile[i].canWrite()) ? "r" : "rw");
|
||||
RAFtime[i] = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called even if not open
|
||||
*/
|
||||
private void closeRAF(int i) throws IOException {
|
||||
RAFtime[i] = 0;
|
||||
if (rafs[i] == null)
|
||||
return;
|
||||
rafs[i].close();
|
||||
rafs[i] = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -40,7 +40,7 @@ import net.i2p.util.Log;
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class TrackerClient extends I2PThread
|
||||
public class TrackerClient extends I2PAppThread
|
||||
{
|
||||
private static final Log _log = new Log(TrackerClient.class);
|
||||
private static final String NO_EVENT = "";
|
||||
@ -57,6 +57,7 @@ public class TrackerClient extends I2PThread
|
||||
private final static int MAX_CONSEC_FAILS = 5; // slow down after this
|
||||
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
|
||||
|
||||
private I2PSnarkUtil _util;
|
||||
private final MetaInfo meta;
|
||||
private final PeerCoordinator coordinator;
|
||||
private final int port;
|
||||
@ -66,10 +67,11 @@ public class TrackerClient extends I2PThread
|
||||
|
||||
private List trackers;
|
||||
|
||||
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
|
||||
public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator)
|
||||
{
|
||||
// Set unique name.
|
||||
super("TrackerClient-" + urlencode(coordinator.getID()));
|
||||
_util = util;
|
||||
this.meta = meta;
|
||||
this.coordinator = coordinator;
|
||||
|
||||
@ -98,13 +100,13 @@ public class TrackerClient extends I2PThread
|
||||
}
|
||||
|
||||
private boolean verifyConnected() {
|
||||
while (!stop && !I2PSnarkUtil.instance().connected()) {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
while (!stop && !_util.connected()) {
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) {
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
return !stop && I2PSnarkUtil.instance().connected();
|
||||
return !stop && _util.connected();
|
||||
}
|
||||
|
||||
public void run()
|
||||
@ -121,7 +123,7 @@ public class TrackerClient extends I2PThread
|
||||
// the primary tracker, that we don't add it twice.
|
||||
trackers = new ArrayList(2);
|
||||
trackers.add(new Tracker(meta.getAnnounce(), true));
|
||||
List tlist = SnarkManager.instance().getOpenTrackers();
|
||||
List tlist = _util.getOpenTrackers();
|
||||
if (tlist != null) {
|
||||
for (int i = 0; i < tlist.size(); i++) {
|
||||
String url = (String)tlist.get(i);
|
||||
@ -136,7 +138,7 @@ public class TrackerClient extends I2PThread
|
||||
}
|
||||
if (meta.getAnnounce().startsWith(url.substring(0, slash)))
|
||||
continue;
|
||||
String dest = I2PSnarkUtil.instance().lookup(url.substring(7, slash));
|
||||
String dest = _util.lookup(url.substring(7, slash));
|
||||
if (dest == null) {
|
||||
_log.error("Announce host unknown: [" + url + "]");
|
||||
continue;
|
||||
@ -264,7 +266,7 @@ public class TrackerClient extends I2PThread
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
_util.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ tr.announce + "': " + ioe, Snark.WARNING);
|
||||
tr.trackerProblems = ioe.getMessage();
|
||||
@ -295,12 +297,12 @@ public class TrackerClient extends I2PThread
|
||||
// we could try and total the unique peers but that's too hard for now
|
||||
coordinator.trackerSeenPeers = maxSeenPeers;
|
||||
if (!started)
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
_util.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
} // *** end of while loop
|
||||
} // try
|
||||
catch (Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
|
||||
_util.debug("TrackerClient: " + t, Snark.ERROR, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
@ -332,19 +334,18 @@ public class TrackerClient extends I2PThread
|
||||
+ "?info_hash=" + infoHash
|
||||
+ "&peer_id=" + peerID
|
||||
+ "&port=" + port
|
||||
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString() + ".i2p"
|
||||
+ "&ip=" + _util.getOurIPString() + ".i2p"
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
_util.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
tr.lastRequestTime = System.currentTimeMillis();
|
||||
File fetched = I2PSnarkUtil.instance().get(s);
|
||||
File fetched = _util.get(s);
|
||||
if (fetched == null) {
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
fetched.deleteOnExit();
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
@ -352,7 +353,7 @@ public class TrackerClient extends I2PThread
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
_util.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
if (failure != null)
|
||||
|
@ -101,7 +101,7 @@ public class TrackerInfo
|
||||
peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||
} catch (InvalidBEncodingException ibe) {
|
||||
// don't let one bad entry spoil the whole list
|
||||
Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
|
||||
//Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
|
||||
continue;
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
|
@ -5,6 +5,7 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -23,7 +24,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.klomp.snark.I2PSnarkUtil;
|
||||
@ -55,13 +56,14 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if ( (configFile == null) || (configFile.trim().length() <= 0) )
|
||||
configFile = "i2psnark.config";
|
||||
_manager.loadConfig(configFile);
|
||||
_manager.start();
|
||||
}
|
||||
|
||||
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
long stats[] = {0,0,0,0};
|
||||
long stats[] = {0,0,0,0,0};
|
||||
|
||||
String nonce = req.getParameter("nonce");
|
||||
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
|
||||
@ -87,8 +89,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td><a href=\"" + req.getRequestURI() + peerString + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a>\n");
|
||||
out.write("<tr><td><a href=\"http://codevoid.i2p/forums/5\" class=\"snarkRefresh\">Wishlist</a>\n");
|
||||
int count = 1;
|
||||
int count = 0;
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry)iter.next();
|
||||
@ -116,7 +117,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
out.write(TABLE_HEADER);
|
||||
if (I2PSnarkUtil.instance().connected() && snarks.size() > 0) {
|
||||
if (_manager.util().connected() && snarks.size() > 0) {
|
||||
if (peerParam != null)
|
||||
out.write("(<a href=\"" + req.getRequestURI() + "\">Hide Peers</a>)<br />\n");
|
||||
else
|
||||
@ -124,7 +125,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
out.write(TABLE_HEADER2);
|
||||
out.write("<th align=\"left\" valign=\"top\">");
|
||||
if (I2PSnarkUtil.instance().connected())
|
||||
if (_manager.util().connected())
|
||||
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
|
||||
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
|
||||
else if (snarks.size() > 0)
|
||||
@ -142,8 +143,10 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if (snarks.size() <= 0) {
|
||||
out.write(TABLE_EMPTY);
|
||||
} else if (snarks.size() > 1) {
|
||||
out.write(TABLE_TOTAL);
|
||||
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
|
||||
out.write("<tfoot><tr>\n" +
|
||||
" <th align=\"left\" valign=\"top\" colspan=\"2\">Totals (" + snarks.size() + " torrents, " + stats[4] + " connected peers)</th>\n" +
|
||||
" <th> </th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
|
||||
@ -199,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);
|
||||
I2PThread fetch = new I2PThread(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
|
||||
}
|
||||
@ -330,7 +337,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
_manager.addMessage("Error creating torrent - you must select a tracker");
|
||||
else if (baseFile.exists()) {
|
||||
try {
|
||||
Storage s = new Storage(baseFile, announceURL, null);
|
||||
Storage s = new Storage(_manager.util(), baseFile, announceURL, null);
|
||||
s.create();
|
||||
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
|
||||
MetaInfo info = s.getMetaInfo();
|
||||
@ -361,8 +368,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if (!snark.stopped)
|
||||
_manager.stopTorrent(snark.torrent, false);
|
||||
}
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().disconnect();
|
||||
if (_manager.util().connected()) {
|
||||
_manager.util().disconnect();
|
||||
_manager.addMessage("I2P tunnel closed");
|
||||
}
|
||||
} else if ("StartAll".equals(action)) {
|
||||
@ -378,7 +385,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
|
||||
private List getSortedSnarks(HttpServletRequest req) {
|
||||
Set files = _manager.listTorrentFiles();
|
||||
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
|
||||
TreeSet fileNames = new TreeSet(Collator.getInstance()); // sorts it alphabetically
|
||||
fileNames.addAll(files);
|
||||
ArrayList rv = new ArrayList(fileNames.size());
|
||||
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
@ -437,6 +445,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if (snark.coordinator != null) {
|
||||
err = snark.coordinator.trackerProblems;
|
||||
curPeers = snark.coordinator.getPeerCount();
|
||||
stats[4] += curPeers;
|
||||
knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
}
|
||||
|
||||
@ -573,8 +582,12 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
client = "I2P-BT";
|
||||
else if ("LUFa".equals(ch))
|
||||
client = "Azureus";
|
||||
else if ("CwsL".equals(ch))
|
||||
client = "I2PSnarkXL";
|
||||
else if ("ZV".equals(ch.substring(2,4)))
|
||||
client = "Robert";
|
||||
else
|
||||
client = "Unknown";
|
||||
client = "Unknown (" + ch + ')';
|
||||
out.write("<font size=-1>" + client + "</font> <tt>" + peer.toString().substring(5, 9) + "</tt>");
|
||||
if (showDebug)
|
||||
out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s");
|
||||
@ -635,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 = "";
|
||||
|
||||
@ -690,8 +703,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String uri = req.getRequestURI();
|
||||
String dataDir = _manager.getDataDir().getAbsolutePath();
|
||||
boolean autoStart = _manager.shouldAutoStart();
|
||||
boolean useOpenTrackers = _manager.shouldUseOpenTrackers();
|
||||
String openTrackers = _manager.getOpenTrackerString();
|
||||
boolean useOpenTrackers = _manager.util().shouldUseOpenTrackers();
|
||||
String openTrackers = _manager.util().getOpenTrackerString();
|
||||
//int seedPct = 0;
|
||||
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
@ -723,9 +736,9 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</select><br />\n");
|
||||
*/
|
||||
out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
|
||||
+ I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
|
||||
+ _manager.util().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
|
||||
out.write("Up bandwidth limit: <input type=\"text\" name=\"upBW\" value=\""
|
||||
+ I2PSnarkUtil.instance().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 2 recommended)</i><br />\n");
|
||||
+ _manager.util().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 2 recommended)</i><br />\n");
|
||||
|
||||
out.write("Use open trackers also: <input type=\"checkbox\" name=\"useOpenTrackers\" value=\"true\" "
|
||||
+ (useOpenTrackers ? "checked " : "")
|
||||
@ -735,15 +748,15 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
|
||||
//out.write("<hr />\n");
|
||||
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
|
||||
+ _manager.util().getEepProxyHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
|
||||
+ _manager.util().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
|
||||
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
|
||||
+ _manager.util().getI2CPHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
|
||||
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
|
||||
+ _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
|
||||
StringBuffer opts = new StringBuffer(64);
|
||||
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
|
||||
Map options = new TreeMap(_manager.util().getI2CPOptions());
|
||||
for (Iterator iter = options.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry)iter.next();
|
||||
String key = (String)entry.getKey();
|
||||
@ -763,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";
|
||||
@ -852,11 +865,6 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
|
||||
|
||||
private static final String TABLE_TOTAL = "<tfoot>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
|
||||
" <th> </th>\n" +
|
||||
" <th> </th>\n";
|
||||
|
||||
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"left\"" +
|
||||
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";
|
||||
@ -877,7 +885,7 @@ class FetchAndAdd implements Runnable {
|
||||
public void run() {
|
||||
_url = _url.trim();
|
||||
// 3 retries
|
||||
File file = I2PSnarkUtil.instance().get(_url, false, 3);
|
||||
File file = _manager.util().get(_url, false, 3);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage("Torrent fetched from " + _url);
|
||||
|
@ -42,7 +42,7 @@
|
||||
</target>
|
||||
<target name="war" depends="precompilejsp">
|
||||
<war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml"
|
||||
basedir="../jsp/" excludes="web.xml, *.java, *.jsp">
|
||||
basedir="../jsp/" excludes="web.xml, **/*.java, *.jsp">
|
||||
</war>
|
||||
</target>
|
||||
<target name="precompilejsp" unless="precompilejsp.uptodate">
|
||||
|
@ -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;
|
||||
@ -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)) {
|
||||
@ -244,6 +248,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runIrcClient(args, l);
|
||||
} else if ("sockstunnel".equals(cmdname)) {
|
||||
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)) {
|
||||
@ -296,6 +306,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
|
||||
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
|
||||
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log("lookup <name>");
|
||||
l.log("quit");
|
||||
l.log("close [forced] <jobnumber>|all");
|
||||
@ -380,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 <host> <port> <privkeyfile>");
|
||||
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
|
||||
@ -494,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]);
|
||||
@ -514,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) {
|
||||
@ -523,7 +584,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("clientTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
|
||||
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>] [<privKeyFile>]");
|
||||
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"
|
||||
@ -555,7 +616,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
String proxy = "squid.i2p";
|
||||
String proxy = "";
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
if ("true".equalsIgnoreCase(args[1].trim())) {
|
||||
@ -595,11 +656,66 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
|
||||
l.log(" <proxy> (optional) indicates a proxy server to be used");
|
||||
l.log(" when trying to access an address out of the .i2p domain");
|
||||
l.log(" (the default proxy is squid.i2p).");
|
||||
notifyEvent("httpclientTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a CONNECT client on the given port number
|
||||
*
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runConnectClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
return;
|
||||
}
|
||||
|
||||
String proxy = "";
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
if ("true".equalsIgnoreCase(args[1].trim())) {
|
||||
isShared = true;
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if ("false".equalsIgnoreCase(args[1].trim())) {
|
||||
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
|
||||
isShared = false;
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if (args.length == 3) {
|
||||
isShared = false; // not "true"
|
||||
proxy = args[2];
|
||||
_log.warn("args[1] == [" + args[1] + "] but rejected");
|
||||
} else {
|
||||
// isShared not specified, default to true
|
||||
isShared = true;
|
||||
proxy = args[1];
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelConnectClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae);
|
||||
}
|
||||
} else {
|
||||
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log(" creates a client that for SSL/HTTPS requests.");
|
||||
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
|
||||
l.log(" <proxy> (optional) indicates a proxy server to be used");
|
||||
l.log(" when trying to access an address out of the .i2p domain");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an IRC client on the given port number
|
||||
*
|
||||
@ -607,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]);
|
||||
@ -638,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) {
|
||||
@ -647,7 +766,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("ircclient <port> [<sharedClient>]");
|
||||
l.log("ircclient <port> [<sharedClient> [<privKeyFile>]]");
|
||||
l.log(" creates a client that filter IRC protocol.");
|
||||
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
|
||||
@ -662,7 +781,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
* "openSOCKSTunnelResult" = "ok" or "error" after the client tunnel has
|
||||
* started.
|
||||
*
|
||||
* @param args {portNumber}
|
||||
* @param args {portNumber [, sharedClient]}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runSOCKSTunnel(String args[], Logging l) {
|
||||
@ -677,6 +796,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isShared = false;
|
||||
if (args.length > 1)
|
||||
isShared = "true".equalsIgnoreCase(args[1].trim());
|
||||
|
||||
ownDest = !isShared;
|
||||
I2PTunnelTask task;
|
||||
task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
@ -688,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 <host> <port> <destination>");
|
||||
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 <port> <privkeyfile>");
|
||||
l.log(" creates a tunnel that sends streaming data.");
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the i2cp host and port
|
||||
*
|
||||
|
@ -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");
|
||||
|
@ -3,6 +3,7 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
@ -27,6 +28,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
|
||||
@ -58,6 +60,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private byte[] pubkey;
|
||||
|
||||
private String handlerName;
|
||||
private String privKeyFile;
|
||||
|
||||
private Object conLock = new Object();
|
||||
|
||||
@ -90,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 privKeyFile 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 });
|
||||
@ -113,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);
|
||||
@ -152,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!");
|
||||
@ -194,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;
|
||||
@ -229,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");
|
||||
@ -301,6 +342,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());
|
||||
}
|
||||
|
||||
@ -321,6 +366,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);
|
||||
@ -373,8 +431,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();
|
||||
}
|
||||
@ -401,7 +461,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
if (_maxWaitTime > 0)
|
||||
SimpleTimer.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
|
||||
SimpleScheduler.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
|
||||
|
||||
synchronized (_waitingSockets) {
|
||||
_waitingSockets.add(s);
|
||||
@ -455,20 +515,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) {
|
||||
@ -476,7 +539,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
return false;
|
||||
}
|
||||
l.log("Client closed.");
|
||||
open = false;
|
||||
}
|
||||
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
|
@ -0,0 +1,369 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
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;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Supports the following:
|
||||
* (where protocol is generally HTTP/1.1 but is ignored)
|
||||
* (where host is one of:
|
||||
* example.i2p
|
||||
* 52chars.b32.i2p
|
||||
* 516+charsbase64
|
||||
* example.com (sent to one of the configured proxies)
|
||||
* )
|
||||
*
|
||||
* (port and protocol are ignored for i2p destinations)
|
||||
* CONNECT host
|
||||
* CONNECT host protocol
|
||||
* CONNECT host:port
|
||||
* CONNECT host:port protocol (this is the standard)
|
||||
*
|
||||
* Additional lines after the CONNECT line but before the blank line are ignored and stripped.
|
||||
* The CONNECT line is removed for .i2p accesses
|
||||
* but passed along for outproxy accesses.
|
||||
*
|
||||
* Ref:
|
||||
* INTERNET-DRAFT Ari Luotonen
|
||||
* Expires: September 26, 1997 Netscape Communications Corporation
|
||||
* <draft-luotonen-ssl-tunneling-03.txt> March 26, 1997
|
||||
* Tunneling SSL Through a WWW Proxy
|
||||
*
|
||||
* @author zzz a stripped-down I2PTunnelHTTPClient
|
||||
*/
|
||||
public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelConnectClient.class);
|
||||
|
||||
private List<String> _proxyList;
|
||||
|
||||
private final static byte[] ERR_DESTINATION_UNKNOWN =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: DESTINATION NOT FOUND</H1>"+
|
||||
"That I2P Destination was not found. "+
|
||||
"The host (or the outproxy, if you're using one) could also "+
|
||||
"be temporarily offline. You may want to <b>retry</b>. "+
|
||||
"Could not find the following Destination:<BR><BR><div>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
|
||||
"Your request was for a site outside of I2P, but you have no "+
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 405 Bad Method\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: METHOD NOT ALLOWED</H1>"+
|
||||
"The request uses a bad protocol. "+
|
||||
"The Connect Proxy supports CONNECT requests ONLY. Other methods such as GET are not allowed - Maybe you wanted the HTTP Proxy?.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_LOCALHOST =
|
||||
("HTTP/1.1 403 Access Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] SUCCESS_RESPONSE =
|
||||
("HTTP/1.1 200 Connection Established\r\n"+
|
||||
"Proxy-agent: I2P\r\n"+
|
||||
"\r\n")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
public I2PTunnelConnectClient(int localPort, Logging l, boolean ownDest,
|
||||
String wwwProxy, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openConnectClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
_proxyList = new ArrayList();
|
||||
if (wwwProxy != null) {
|
||||
StringTokenizer tok = new StringTokenizer(wwwProxy, ",");
|
||||
while (tok.hasMoreTokens())
|
||||
_proxyList.add(tok.nextToken().trim());
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> ConnectClient [Outproxy list: " + wwwProxy + "]");
|
||||
|
||||
startRunning();
|
||||
}
|
||||
|
||||
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
private String selectProxy() {
|
||||
synchronized (_proxyList) {
|
||||
int size = _proxyList.size();
|
||||
if (size <= 0)
|
||||
return null;
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return _proxyList.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
|
||||
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
|
||||
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
String targetRequest = null;
|
||||
boolean usingWWWProxy = false;
|
||||
String currentProxy = null;
|
||||
long requestId = ++__requestId;
|
||||
try {
|
||||
out = s.getOutputStream();
|
||||
in = s.getInputStream();
|
||||
String line, method = null, host = null, destination = null, restofline = null;
|
||||
StringBuffer newRequest = new StringBuffer();
|
||||
int ahelper = 0;
|
||||
while (true) {
|
||||
// Use this rather than BufferedReader because we can't have readahead,
|
||||
// since we are passing the stream on to I2PTunnelRunner
|
||||
line = DataHelper.readLine(in);
|
||||
line = line.trim();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
|
||||
|
||||
if (method == null) { // first line CONNECT blah.i2p:80 HTTP/1.1
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break; // empty first line
|
||||
method = line.substring(0, pos);
|
||||
String request = line.substring(pos + 1);
|
||||
|
||||
pos = request.indexOf(":");
|
||||
if (pos == -1)
|
||||
pos = request.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
host = request;
|
||||
restofline = "";
|
||||
} else {
|
||||
host = request.substring(0, pos);
|
||||
restofline = request.substring(pos); // ":80 HTTP/1.1" or " HTTP/1.1"
|
||||
}
|
||||
|
||||
if (host.toLowerCase().endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
destination = host;
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// The request must be forwarded to a outproxy
|
||||
currentProxy = selectProxy();
|
||||
if (currentProxy == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!");
|
||||
writeErrorMessage(ERR_NO_OUTPROXY, out);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
destination = currentProxy;
|
||||
usingWWWProxy = true;
|
||||
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n\r\n"); // HTTP spec
|
||||
} else if (host.toLowerCase().equals("localhost")) {
|
||||
writeErrorMessage(ERR_LOCALHOST, out);
|
||||
s.close();
|
||||
return;
|
||||
} else { // full b64 address (hopefully)
|
||||
destination = host;
|
||||
}
|
||||
targetRequest = host;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getPrefix(requestId) + "METHOD:" + method + ":");
|
||||
_log.debug(getPrefix(requestId) + "HOST :" + host + ":");
|
||||
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
|
||||
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
|
||||
}
|
||||
|
||||
} else if (line.length() > 0) {
|
||||
// Additional lines - shouldn't be too many. Firefox sends:
|
||||
// User-Agent: blabla
|
||||
// Proxy-Connection: keep-alive
|
||||
// Host: blabla.i2p
|
||||
//
|
||||
// We could send these (filtered like in HTTPClient) on to the outproxy,
|
||||
// but for now just chomp them all.
|
||||
line = null;
|
||||
} else {
|
||||
// do it
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (destination == null || !"CONNECT".equalsIgnoreCase(method)) {
|
||||
writeErrorMessage(ERR_BAD_PROTOCOL, out);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions());
|
||||
byte[] data = null;
|
||||
byte[] response = null;
|
||||
if (usingWWWProxy)
|
||||
data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
else
|
||||
response = SUCCESS_RESPONSE;
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (IOException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (I2PException ex) {
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
IOException ex = new IOException("OOM");
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OnTimeout implements Runnable {
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
private boolean _usingProxy;
|
||||
private String _wwwProxy;
|
||||
private long _requestId;
|
||||
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
|
||||
_socket = s;
|
||||
_out = out;
|
||||
_target = target;
|
||||
_usingProxy = usingProxy;
|
||||
_wwwProxy = wwwProxy;
|
||||
_requestId = id;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Timeout occured requesting " + _target);
|
||||
handleConnectClientException(new RuntimeException("Timeout"), _out,
|
||||
_target, _usingProxy, _wwwProxy, _requestId);
|
||||
closeSocket(_socket);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out) throws IOException {
|
||||
if (out == null)
|
||||
return;
|
||||
out.write(errMessage);
|
||||
out.write("\n</body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
out.write(targetRequest.getBytes());
|
||||
if (usingWWWProxy)
|
||||
out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
}
|
||||
out.write("</div>".getBytes());
|
||||
out.write("\n</body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleConnectClientException(Exception ex, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy, long requestId) {
|
||||
if (out == null)
|
||||
return;
|
||||
try {
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
@ -21,6 +22,7 @@ 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;
|
||||
import net.i2p.util.FileUtil;
|
||||
@ -131,16 +133,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static int MAX_POSTBYTES = 20*1024*1024; // arbitrary but huge - all in memory, no temp file
|
||||
private final static byte[] ERR_MAXPOST =
|
||||
("HTTP/1.1 503 Bad POST\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"The maximum POST size is " + MAX_POSTBYTES + " bytes.<BR>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@ -193,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();
|
||||
@ -218,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);
|
||||
@ -232,6 +227,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
String targetRequest = null;
|
||||
boolean usingWWWProxy = false;
|
||||
@ -239,11 +235,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
long requestId = ++__requestId;
|
||||
try {
|
||||
out = s.getOutputStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1"));
|
||||
InputReader reader = new InputReader(s.getInputStream());
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuffer newRequest = new StringBuffer();
|
||||
int ahelper = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
while ((line = reader.readLine(method)) != null) {
|
||||
line = line.trim();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
|
||||
|
||||
@ -257,7 +254,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Method is null for [" + line + "]");
|
||||
|
||||
line = line.trim();
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method = line.substring(0, pos);
|
||||
@ -470,7 +466,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Setting host = " + host);
|
||||
} else if (lowercaseLine.startsWith("user-agent: ") &&
|
||||
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_USER_AGENT)).booleanValue()) {
|
||||
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) {
|
||||
line = null;
|
||||
continue;
|
||||
} else if (lowercaseLine.startsWith("accept")) {
|
||||
@ -479,13 +475,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
line = null;
|
||||
continue;
|
||||
} else if (lowercaseLine.startsWith("referer: ") &&
|
||||
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_REFERER)).booleanValue()) {
|
||||
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_REFERER)).booleanValue()) {
|
||||
// Shouldn't we be more specific, like accepting in-site referers ?
|
||||
//line = "Referer: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (lowercaseLine.startsWith("via: ") &&
|
||||
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_VIA)).booleanValue()) {
|
||||
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_VIA)).booleanValue()) {
|
||||
//line = "Via: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
@ -498,7 +494,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
if (line.length() == 0) {
|
||||
|
||||
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
|
||||
String ok = getTunnel().getClientOptions().getProperty("i2ptunnel.gzip");
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
gzip = Boolean.valueOf(ok).booleanValue();
|
||||
@ -509,35 +505,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
newRequest.append("Accept-Encoding: \r\n");
|
||||
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
|
||||
}
|
||||
if (!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_USER_AGENT)).booleanValue())
|
||||
if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue())
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
newRequest.append(line.trim()).append("\r\n"); // HTTP spec
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
|
||||
|
||||
int postbytes = 0;
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
newRequest.append((char) i);
|
||||
if (++postbytes > MAX_POSTBYTES) {
|
||||
if (out != null) {
|
||||
out.write(ERR_MAXPOST);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method == null || destination == null) {
|
||||
l.log("No HTTP method found in the request.");
|
||||
@ -560,7 +537,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
//l.log("Could not resolve " + destination + ".");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
|
||||
String str;
|
||||
@ -570,6 +547,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else if(ahelper != 0)
|
||||
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
|
||||
else if (destination.length() == 60 && destination.endsWith(".b32.i2p"))
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
else {
|
||||
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
|
||||
showAddrHelper = true;
|
||||
@ -608,8 +587,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (OutOfMemoryError oom) { // mainly for huge POSTs
|
||||
IOException ex = new IOException("OOM (in POST?)");
|
||||
} catch (OutOfMemoryError oom) {
|
||||
IOException ex = new IOException("OOM");
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
@ -617,6 +596,29 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the first line unbuffered.
|
||||
* After that, switch to a BufferedReader, unless the method is "POST".
|
||||
* We can't use BufferedReader for POST because we can't have readahead,
|
||||
* since we are passing the stream on to I2PTunnelRunner for the POST data.
|
||||
*
|
||||
*/
|
||||
private static class InputReader {
|
||||
BufferedReader _br;
|
||||
InputStream _s;
|
||||
public InputReader(InputStream s) {
|
||||
_br = null;
|
||||
_s = s;
|
||||
}
|
||||
String readLine(String method) throws IOException {
|
||||
if (method == null || "POST".equals(method))
|
||||
return DataHelper.readLine(_s);
|
||||
if (_br == null)
|
||||
_br = new BufferedReader(new InputStreamReader(_s, "ISO-8859-1"));
|
||||
return _br.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
private final static String getHostName(String host) {
|
||||
if (host == null) return null;
|
||||
try {
|
||||
@ -628,7 +630,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private class OnTimeout implements Runnable {
|
||||
private static class OnTimeout implements Runnable {
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
@ -706,11 +708,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHTTPClientException(Exception ex, OutputStream out, String targetRequest,
|
||||
private static void handleHTTPClientException(Exception ex, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy, long requestId) {
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
|
||||
// static
|
||||
//if (_log.shouldLog(Log.WARN))
|
||||
// _log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
|
||||
if (out != null) {
|
||||
try {
|
||||
String str;
|
||||
@ -725,16 +728,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
|
||||
// static
|
||||
//_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
|
||||
}
|
||||
} else {
|
||||
_log.warn(getPrefix(requestId) + "Client disconnected before we could say that destination " + "was unknown", ex);
|
||||
// static
|
||||
//_log.warn(getPrefix(requestId) + "Client disconnected before we could say that destination " + "was unknown", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final static String SUPPORTED_HOSTS[] = { "i2p", "www.i2p.com", "i2p."};
|
||||
|
||||
private boolean isSupportedAddress(String host, String protocol) {
|
||||
private static boolean isSupportedAddress(String host, String protocol) {
|
||||
if ((host == null) || (protocol == null)) return false;
|
||||
boolean found = false;
|
||||
String lcHost = host.toLowerCase();
|
||||
|
@ -124,8 +124,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 +171,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.<init>(FileInputStream.java:131)
|
||||
// at java.net.SocketInputStream.<init>(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))
|
||||
|
@ -39,12 +39,12 @@ 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);
|
||||
@ -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))
|
||||
|
@ -0,0 +1,191 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.DataFormatException;
|
||||
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.I2PThread;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
@ -14,6 +14,7 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@ -57,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();
|
||||
}
|
||||
@ -72,7 +73,7 @@ public class TunnelController implements Logging {
|
||||
|
||||
File keyFile = new File(getPrivKeyFile());
|
||||
if (keyFile.exists()) {
|
||||
log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
|
||||
//log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
|
||||
return;
|
||||
} else {
|
||||
File parent = keyFile.getParentFile();
|
||||
@ -86,6 +87,7 @@ public class TunnelController implements Logging {
|
||||
String destStr = dest.toBase64();
|
||||
log("Private key created and saved in " + keyFile.getAbsolutePath());
|
||||
log("New destination: " + destStr);
|
||||
log("Base32: " + Base32.encode(dest.calculateHash().getData()) + ".b32.i2p");
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error creating new destination", ie);
|
||||
@ -132,25 +134,38 @@ 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)) {
|
||||
} else if("ircclient".equals(type)) {
|
||||
startIrcClient();
|
||||
} else if("sockstunnel".equals(type)) {
|
||||
startSocksClient();
|
||||
} else if("connectclient".equals(type)) {
|
||||
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();
|
||||
@ -159,20 +174,62 @@ 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() {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String proxyList = getProxyList();
|
||||
String sharedClient = getSharedClient();
|
||||
if (proxyList == null)
|
||||
_tunnel.runConnectClient(new String[] { listenPort, sharedClient }, this);
|
||||
else
|
||||
_tunnel.runConnectClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
}
|
||||
|
||||
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() {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,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() {
|
||||
@ -348,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();
|
||||
@ -361,6 +419,19 @@ public class TunnelController implements Logging {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getMyDestHashBase32() {
|
||||
if (_tunnel != null) {
|
||||
List sessions = _tunnel.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null)
|
||||
return Base32.encode(dest.calculateHash().getData());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean getIsRunning() { return _running; }
|
||||
public boolean getIsStarting() { return _starting; }
|
||||
|
||||
|
@ -7,6 +7,12 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.Destination;
|
||||
@ -20,7 +26,7 @@ import net.i2p.util.Log;
|
||||
public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
|
||||
private static final Log _log = new Log(I2PSOCKSTunnel.class);
|
||||
|
||||
private HashMap<String, List<String>> proxies = null; // port# + "" or "default" -> hostname list
|
||||
protected Destination outProxyDest = null;
|
||||
|
||||
//public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest) {
|
||||
@ -36,7 +42,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> SOCKSTunnel");
|
||||
|
||||
parseOptions();
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openSOCKSTunnelResult", "ok");
|
||||
@ -46,11 +52,49 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
try {
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
|
||||
} catch (SOCKSException e) {
|
||||
_log.error("Error from SOCKS connection: " + e.getMessage());
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String PROP_PROXY = "i2ptunnel.socks.proxy.";
|
||||
private void parseOptions() {
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
proxies = new HashMap(0);
|
||||
for (Map.Entry e : opts.entrySet()) {
|
||||
String prop = (String)e.getKey();
|
||||
if ((!prop.startsWith(PROP_PROXY)) || prop.length() <= PROP_PROXY.length())
|
||||
continue;
|
||||
String port = prop.substring(PROP_PROXY.length());
|
||||
List proxyList = new ArrayList(1);
|
||||
StringTokenizer tok = new StringTokenizer((String)e.getValue(), ", \t");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String proxy = tok.nextToken().trim();
|
||||
if (proxy.endsWith(".i2p"))
|
||||
proxyList.add(proxy);
|
||||
else
|
||||
_log.error("Non-i2p SOCKS outproxy: " + proxy);
|
||||
}
|
||||
proxies.put(port, proxyList);
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, List<String>> getProxyMap() {
|
||||
return proxies;
|
||||
}
|
||||
|
||||
public List<String> getProxies(int port) {
|
||||
List<String> rv = proxies.get(port + "");
|
||||
if (rv == null)
|
||||
rv = getDefaultProxies();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public List<String> getDefaultProxies() {
|
||||
return proxies.get("default");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Destination, Sink> cache;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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<Destination, Sink> cache;
|
||||
private Sink sink;
|
||||
}
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
@ -12,7 +12,17 @@ import java.io.DataOutputStream;
|
||||
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;
|
||||
|
||||
@ -28,7 +38,6 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
private static final int SOCKS_VERSION_5 = 0x05;
|
||||
|
||||
private Socket clientSock = null;
|
||||
|
||||
private boolean setupCompleted = false;
|
||||
|
||||
/**
|
||||
@ -61,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() + ")");
|
||||
}
|
||||
@ -105,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?)");
|
||||
@ -121,11 +131,15 @@ 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);
|
||||
throw new SOCKSException("Invalid command in request");
|
||||
}
|
||||
|
||||
@ -145,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:
|
||||
{
|
||||
@ -161,19 +176,25 @@ 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);
|
||||
throw new SOCKSException("Invalid addresses type in request");
|
||||
}
|
||||
|
||||
connPort = in.readUnsignedShort();
|
||||
if (connPort == 0) {
|
||||
_log.debug("trying to connect to TCP port 0? Dropping!");
|
||||
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 {
|
||||
@ -248,27 +269,188 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
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, SOCKS5, HTTP CONNECT...).
|
||||
I2PSocket destSock;
|
||||
|
||||
try {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_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);
|
||||
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";
|
||||
_log.error(err);
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 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";
|
||||
_log.error(err);
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException(err);
|
||||
} else {
|
||||
List<String> proxies = t.getProxies(connPort);
|
||||
if (proxies == null || proxies.size() <= 0) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either";
|
||||
_log.error(err);
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 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.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error in destination format");
|
||||
} catch (SocketException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (I2PException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
|
||||
// This isn't really the right place for this, we can't stop the tunnel once it starts.
|
||||
static SOCKSUDPTunnel _tunnel;
|
||||
static 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<Integer> 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
|
||||
*/
|
||||
private class Method {
|
||||
private static class Method {
|
||||
private static final int NO_AUTH_REQUIRED = 0x00;
|
||||
private static final int NO_ACCEPTABLE_METHODS = 0xff;
|
||||
}
|
||||
|
||||
private class AddressType {
|
||||
private static class AddressType {
|
||||
private static final int IPV4 = 0x01;
|
||||
private static final int DOMAINNAME = 0x03;
|
||||
private static final int IPV6 = 0x04;
|
||||
}
|
||||
|
||||
private class Command {
|
||||
private static class Command {
|
||||
private static final int CONNECT = 0x01;
|
||||
private static final int BIND = 0x02;
|
||||
private static final int UDP_ASSOCIATE = 0x03;
|
||||
}
|
||||
|
||||
private class Reply {
|
||||
private static class Reply {
|
||||
private static final int SUCCEEDED = 0x00;
|
||||
private static final int GENERAL_SOCKS_SERVER_FAILURE = 0x01;
|
||||
private static final int CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02;
|
||||
@ -279,4 +461,4 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
private static final int COMMAND_NOT_SUPPORTED = 0x07;
|
||||
private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -6,15 +6,9 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -30,10 +24,6 @@ public abstract class SOCKSServer {
|
||||
protected String connHostName = null;
|
||||
protected int connPort = 0;
|
||||
|
||||
I2PSocket destSocket = null;
|
||||
|
||||
Object FIXME = new Object();
|
||||
|
||||
/**
|
||||
* Perform server initialization (expecially regarding protected
|
||||
* variables).
|
||||
@ -59,47 +49,6 @@ public abstract class SOCKSServer {
|
||||
*
|
||||
* @return an I2PSocket connected with the destination
|
||||
*/
|
||||
public I2PSocket getDestinationI2PSocket() throws SOCKSException {
|
||||
setupServer();
|
||||
public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException;
|
||||
|
||||
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!");
|
||||
}
|
||||
|
||||
// FIXME: here we should read our config file, select an
|
||||
// outproxy, and instantiate the proper socket class that
|
||||
// handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...).
|
||||
I2PSocket destSock;
|
||||
|
||||
try {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_log.debug("connecting to " + connHostName + "...");
|
||||
I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} else {
|
||||
_log.error("We don't support outproxies (yet)");
|
||||
throw new SOCKSException("Ouproxies not supported (yet)");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
throw new SOCKSException("Error in destination format");
|
||||
} catch (SocketException e) {
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (I2PException e) {
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
@ -18,6 +19,15 @@ import net.i2p.util.Log;
|
||||
public class SOCKSServerFactory {
|
||||
private final static Log _log = new Log(SOCKSServerFactory.class);
|
||||
|
||||
private final static String ERR_REQUEST_DENIED =
|
||||
"HTTP/1.1 403 Access Denied\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P SOCKS PROXY ERROR: REQUEST DENIED</H1>" +
|
||||
"Your browser is misconfigured. This is a SOCKS proxy, not a HTTP proxy" +
|
||||
"</body></html>";
|
||||
|
||||
/**
|
||||
* Create a new SOCKS server, using the provided socket (that must
|
||||
* be connected to a client) to select the proper SOCKS protocol
|
||||
@ -34,13 +44,23 @@ 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);
|
||||
break;
|
||||
case 'C':
|
||||
case 'G':
|
||||
case 'H':
|
||||
case 'P':
|
||||
DataOutputStream out = new DataOutputStream(s.getOutputStream());
|
||||
out.write(ERR_REQUEST_DENIED.getBytes());
|
||||
throw new SOCKSException("HTTP request to socks");
|
||||
default:
|
||||
_log.debug("SOCKS protocol version not supported (" + Integer.toHexString(socksVer) + ")");
|
||||
return null;
|
||||
throw new SOCKSException("SOCKS protocol version not supported (" + Integer.toHexString(socksVer) + ")");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
_log.debug("error reading SOCKS protocol version");
|
||||
@ -49,4 +69,4 @@ public class SOCKSServerFactory {
|
||||
|
||||
return serv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
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.Logging;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
|
||||
/**
|
||||
* 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<Destination, SOCKSUDPPort> e = (Map.Entry) iter.next();
|
||||
if (e.getValue() == sup)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public final void startRunning() {
|
||||
super.startRunning();
|
||||
// demuxer start() doesn't do anything
|
||||
startall();
|
||||
}
|
||||
|
||||
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<Integer, SOCKSUDPPort> ports;
|
||||
private Map<Destination, SOCKSUDPPort> cache;
|
||||
private MultiSink demuxer;
|
||||
}
|
@ -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<Destination, SOCKSHeader> 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<Destination, SOCKSHeader> cache;
|
||||
}
|
@ -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<Destination, SOCKSHeader> 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<Destination, SOCKSHeader> cache;
|
||||
}
|
@ -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<Destination>();
|
||||
}
|
||||
|
||||
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<Destination> sinks;
|
||||
}
|
@ -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;
|
||||
protected boolean running;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.data.Destination;
|
||||
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);
|
||||
}
|
||||
|
||||
public final void startRunning() {
|
||||
super.startRunning();
|
||||
// send subscribe-message
|
||||
this.pinger.start();
|
||||
l.log("Streamr client ready");
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
// send unsubscribe-message
|
||||
this.pinger.stop();
|
||||
this.sink.stop();
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private UDPSink sink;
|
||||
private Pinger pinger;
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.client.I2PSession;
|
||||
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);
|
||||
}
|
||||
|
||||
public final void startRunning() {
|
||||
super.startRunning();
|
||||
this.server.start();
|
||||
l.log("Streamr server ready");
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
this.server.stop();
|
||||
this.multi.stop();
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private MultiSource multi;
|
||||
private UDPSource server;
|
||||
private Sink subscriber;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
// system
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
// i2p
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
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;
|
||||
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<Destination>();
|
||||
}
|
||||
|
||||
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<Destination> subscriptions;
|
||||
private MultiSource multi;
|
||||
private Source server;
|
||||
}
|
71
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java
Normal file
71
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java
Normal file
@ -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 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)
|
||||
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;
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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)
|
||||
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;
|
||||
}
|
123
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
Normal file
123
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
Normal file
@ -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<Integer> queue;
|
||||
protected Sink sink;
|
||||
protected Thread thread;
|
||||
protected boolean verify;
|
||||
protected boolean raw;
|
||||
}
|
17
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java
Normal file
17
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Sink.java
Normal file
@ -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);
|
||||
}
|
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java
Normal file
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Source.java
Normal file
@ -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();
|
||||
}
|
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java
Normal file
15
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/Stream.java
Normal file
@ -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();
|
||||
}
|
77
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java
Normal file
77
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSink.java
Normal file
@ -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;
|
||||
|
||||
}
|
91
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
Normal file
91
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
Normal file
@ -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;
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/* 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.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
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.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.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*
|
||||
* @author zzz with portions from welterde's streamr
|
||||
*/
|
||||
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 dest;
|
||||
byte[] key;
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
dest = 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel.udpTunnel;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Iterator;
|
||||
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.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Base64;
|
||||
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.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink {
|
||||
|
||||
private final static Log _log = new Log(I2PTunnelUDPServerBase.class);
|
||||
|
||||
private 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;
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*
|
||||
* @author zzz with portions from welterde's streamr
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package net.i2p.i2ptunnel.web;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
@ -28,9 +29,7 @@ public class EditBean extends IndexBean {
|
||||
if (controllers.size() > tunnel) {
|
||||
TunnelController cur = (TunnelController)controllers.get(tunnel);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) ||
|
||||
("httpclient".equals(cur.getType()))||
|
||||
("ircclient".equals(cur.getType())));
|
||||
return isClient(cur.getType());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -38,7 +37,7 @@ public class EditBean extends IndexBean {
|
||||
|
||||
public String getTargetHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
if (tun != null && tun.getTargetHost() != null)
|
||||
return tun.getTargetHost();
|
||||
else
|
||||
return "127.0.0.1";
|
||||
@ -52,7 +51,7 @@ public class EditBean extends IndexBean {
|
||||
}
|
||||
public String getSpoofedHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
if (tun != null && tun.getSpoofedHost() != null)
|
||||
return tun.getSpoofedHost();
|
||||
else
|
||||
return "";
|
||||
@ -61,8 +60,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) {
|
||||
@ -82,119 +82,123 @@ public class EditBean extends IndexBean {
|
||||
}
|
||||
|
||||
public boolean shouldDelay(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String delay = opts.getProperty("i2p.streaming.connectDelay");
|
||||
if ( (delay == null) || ("0".equals(delay)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return getProperty(tunnel, "i2p.streaming.connectDelay", 0) > 0;
|
||||
}
|
||||
|
||||
public boolean isInteractive(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
|
||||
if ( (wsiz == null) || (!"1".equals(wsiz)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return getProperty(tunnel, "i2p.streaming.maxWindowSize", 128) == 12;
|
||||
}
|
||||
|
||||
public int getTunnelDepth(int tunnel, int defaultLength) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.length");
|
||||
if (len == null) return defaultLength;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
return getProperty(tunnel, "inbound.length", defaultLength);
|
||||
}
|
||||
|
||||
public int getTunnelQuantity(int tunnel, int defaultQuantity) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.quantity");
|
||||
if (len == null) return defaultQuantity;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultQuantity;
|
||||
}
|
||||
return getProperty(tunnel, "inbound.quantity", defaultQuantity);
|
||||
}
|
||||
|
||||
public int getTunnelBackupQuantity(int tunnel, int defaultBackupQuantity) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.backupQuantity");
|
||||
if (len == null) return defaultBackupQuantity;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultBackupQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultBackupQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultBackupQuantity;
|
||||
}
|
||||
return getProperty(tunnel, "inbound.backupQuantity", defaultBackupQuantity);
|
||||
}
|
||||
|
||||
public int getTunnelVariance(int tunnel, int defaultVariance) {
|
||||
return getProperty(tunnel, "inbound.lengthVariance", defaultVariance);
|
||||
}
|
||||
|
||||
public boolean getReduce(int tunnel) {
|
||||
return getBooleanProperty(tunnel, "i2cp.reduceOnIdle");
|
||||
}
|
||||
|
||||
public int getReduceCount(int tunnel) {
|
||||
return getProperty(tunnel, "i2cp.reduceQuantity", 1);
|
||||
}
|
||||
|
||||
public int getReduceTime(int tunnel) {
|
||||
return getProperty(tunnel, "i2cp.reduceIdleTime", 20*60*1000) / (60*1000);
|
||||
}
|
||||
|
||||
public int getCert(int tunnel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getEffort(int tunnel) {
|
||||
return 23;
|
||||
}
|
||||
|
||||
public String getSigner(int tunnel) {
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean getEncrypt(int tunnel) {
|
||||
return getBooleanProperty(tunnel, "i2cp.encryptLeaseSet");
|
||||
}
|
||||
|
||||
public String getEncryptKey(int tunnel) {
|
||||
return getProperty(tunnel, "i2cp.leaseSetKey", "");
|
||||
}
|
||||
|
||||
public boolean getAccess(int tunnel) {
|
||||
return getBooleanProperty(tunnel, "i2cp.enableAccessList");
|
||||
}
|
||||
|
||||
public String getAccessList(int tunnel) {
|
||||
return getProperty(tunnel, "i2cp.accessList", "").replaceAll(",", "\n");
|
||||
}
|
||||
|
||||
public boolean getClose(int tunnel) {
|
||||
return getBooleanProperty(tunnel, "i2cp.closeOnIdle");
|
||||
}
|
||||
|
||||
public int getCloseTime(int tunnel) {
|
||||
return getProperty(tunnel, "i2cp.closeIdleTime", 30*60*1000) / (60*1000);
|
||||
}
|
||||
|
||||
public boolean getNewDest(int tunnel) {
|
||||
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) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.lengthVariance");
|
||||
if (len == null) return defaultVariance;
|
||||
String s = opts.getProperty(prop);
|
||||
if (s == null) return def;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultVariance;
|
||||
}
|
||||
} else {
|
||||
return defaultVariance;
|
||||
return Integer.parseInt(s);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
} else {
|
||||
return defaultVariance;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
private String getProperty(int tunnel, String prop, String def) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null)
|
||||
return opts.getProperty(prop, def);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/** default is false */
|
||||
private boolean getBooleanProperty(int tunnel, String prop) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null)
|
||||
return Boolean.valueOf(opts.getProperty(prop)).booleanValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getI2CPHost(int tunnel) {
|
||||
@ -222,19 +226,9 @@ public class EditBean extends IndexBean {
|
||||
int i = 0;
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
if (_noShowSet.contains(key))
|
||||
continue;
|
||||
String val = opts.getProperty(key);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.lengthVariance".equals(key)) continue;
|
||||
if ("outbound.lengthVariance".equals(key)) continue;
|
||||
if ("inbound.backupQuantity".equals(key)) continue;
|
||||
if ("outbound.backupQuantity".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
|
@ -8,13 +8,24 @@ package net.i2p.i2ptunnel.web;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -57,6 +68,11 @@ public class IndexBean {
|
||||
private boolean _sharedClient;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
private Set<String> _booleanOptions;
|
||||
private Map<String, String> _otherOptions;
|
||||
private int _hashCashValue;
|
||||
private int _certType;
|
||||
private String _certSigner;
|
||||
|
||||
public static final int RUNNING = 1;
|
||||
public static final int STARTING = 2;
|
||||
@ -85,6 +101,8 @@ public class IndexBean {
|
||||
} catch (NumberFormatException nfe) {}
|
||||
_nextNonce = _context.random().nextLong();
|
||||
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
|
||||
_booleanOptions = new ConcurrentHashSet(4);
|
||||
_otherOptions = new ConcurrentHashMap(4);
|
||||
}
|
||||
|
||||
public long getNextNonce() { return _nextNonce; }
|
||||
@ -146,6 +164,12 @@ public class IndexBean {
|
||||
else if ("Delete this proxy".equals(_action) || // IE workaround:
|
||||
(_action.toLowerCase().indexOf("d</span>elete") >= 0))
|
||||
return deleteTunnel();
|
||||
else if ("Estimate".equals(_action))
|
||||
return PrivateKeyFile.estimateHashCashTime(_hashCashValue);
|
||||
else if ("Modify".equals(_action))
|
||||
return modifyDestination();
|
||||
else if ("Generate".equals(_action))
|
||||
return generateNewEncryptionKey();
|
||||
else
|
||||
return "Action " + _action + " unknown";
|
||||
}
|
||||
@ -209,10 +233,7 @@ public class IndexBean {
|
||||
}
|
||||
// Only modify other shared tunnels
|
||||
// if the current tunnel is shared, and of supported type
|
||||
if ("true".equalsIgnoreCase(cur.getSharedClient()) &&
|
||||
("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType()))) {
|
||||
if ("true".equalsIgnoreCase(cur.getSharedClient()) && isClient(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
|
||||
@ -224,11 +245,7 @@ public class IndexBean {
|
||||
|
||||
// Only modify this non-current tunnel
|
||||
// if it belongs to a shared destination, and is of supported type
|
||||
if ("true".equalsIgnoreCase(c.getSharedClient()) &&
|
||||
("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType()) ||
|
||||
"client".equals(c.getType()))) {
|
||||
|
||||
if ("true".equalsIgnoreCase(c.getSharedClient()) && isClient(c.getType())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelQuantity != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
|
||||
@ -326,9 +343,16 @@ public class IndexBean {
|
||||
public boolean isClient(int tunnelNum) {
|
||||
TunnelController cur = getController(tunnelNum);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) ||
|
||||
("httpclient".equals(cur.getType())) ||
|
||||
("ircclient".equals(cur.getType())));
|
||||
return isClient(cur.getType());
|
||||
}
|
||||
|
||||
public static boolean isClient(String type) {
|
||||
return ( ("client".equals(type)) ||
|
||||
("httpclient".equals(type)) ||
|
||||
("sockstunnel".equals(type)) ||
|
||||
("connectclient".equals(type)) ||
|
||||
("streamrclient".equals(type)) ||
|
||||
("ircclient".equals(type)));
|
||||
}
|
||||
|
||||
public String getTunnelName(int tunnel) {
|
||||
@ -361,6 +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 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;
|
||||
}
|
||||
|
||||
@ -407,13 +436,13 @@ public class IndexBean {
|
||||
public String getClientDestination(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return "";
|
||||
if ("client".equals(tun.getType())||"ircclient".equals(tun.getType())) {
|
||||
if (tun.getTargetDestination() != null)
|
||||
return tun.getTargetDestination();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
else return tun.getProxyList();
|
||||
String rv;
|
||||
if ("client".equals(tun.getType()) || "ircclient".equals(tun.getType()) ||
|
||||
"streamrclient".equals(tun.getType()))
|
||||
rv = tun.getTargetDestination();
|
||||
else
|
||||
rv = tun.getProxyList();
|
||||
return rv != null ? rv : "";
|
||||
}
|
||||
|
||||
public String getServerTarget(int tunnel) {
|
||||
@ -430,11 +459,28 @@ public class IndexBean {
|
||||
String rv = tun.getMyDestination();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
else
|
||||
return "";
|
||||
} else {
|
||||
return "";
|
||||
// if not running, do this the hard way
|
||||
String keyFile = tun.getPrivKeyFile();
|
||||
if (keyFile != null && keyFile.trim().length() > 0) {
|
||||
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
|
||||
try {
|
||||
Destination d = pkf.getDestination();
|
||||
if (d != null)
|
||||
return d.toBase64();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getDestHashBase32(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
String rv = tun.getMyDestHashBase32();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
///
|
||||
@ -556,6 +602,164 @@ public class IndexBean {
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
public void setReduce(String moo) {
|
||||
_booleanOptions.add("i2cp.reduceOnIdle");
|
||||
}
|
||||
public void setClose(String moo) {
|
||||
_booleanOptions.add("i2cp.closeOnIdle");
|
||||
}
|
||||
public void setEncrypt(String moo) {
|
||||
_booleanOptions.add("i2cp.encryptLeaseSet");
|
||||
}
|
||||
public void setAccess(String moo) {
|
||||
_booleanOptions.add("i2cp.enableAccessList");
|
||||
}
|
||||
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) {
|
||||
if (val != null) {
|
||||
try {
|
||||
_otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
public void setReduceCount(String val) {
|
||||
if (val != null)
|
||||
_otherOptions.put("i2cp.reduceQuantity", val.trim());
|
||||
}
|
||||
public void setEncryptKey(String val) {
|
||||
if (val != null)
|
||||
_otherOptions.put("i2cp.leaseSetKey", val.trim());
|
||||
}
|
||||
public void setAccessList(String val) {
|
||||
if (val != null)
|
||||
_otherOptions.put("i2cp.accessList", val.trim().replaceAll("\r\n", ",").replaceAll("\n", ",").replaceAll(" ", ","));
|
||||
}
|
||||
public void setCloseTime(String val) {
|
||||
if (val != null) {
|
||||
try {
|
||||
_otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** params needed for hashcash and dest modification */
|
||||
public void setEffort(String val) {
|
||||
if (val != null) {
|
||||
try {
|
||||
_hashCashValue = Integer.parseInt(val.trim());
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
public void setCert(String val) {
|
||||
if (val != null) {
|
||||
try {
|
||||
_certType = Integer.parseInt(val.trim());
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
public void setSigner(String val) {
|
||||
_certSigner = val;
|
||||
}
|
||||
|
||||
/** Modify or create a destination */
|
||||
private String modifyDestination() {
|
||||
if (_privKeyFile == null || _privKeyFile.trim().length() <= 0)
|
||||
return "Private Key File not specified";
|
||||
|
||||
TunnelController tun = getController(_tunnel);
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "Invalid params";
|
||||
if (tun == null) {
|
||||
// creating new
|
||||
tun = new TunnelController(config, "", true);
|
||||
_group.addController(tun);
|
||||
saveChanges();
|
||||
} else if (tun.getIsRunning() || tun.getIsStarting()) {
|
||||
return "Tunnel must be stopped before modifying destination";
|
||||
}
|
||||
PrivateKeyFile pkf = new PrivateKeyFile(_privKeyFile);
|
||||
try {
|
||||
pkf.createIfAbsent();
|
||||
} catch (Exception e) {
|
||||
return "Create private key file failed: " + e;
|
||||
}
|
||||
switch (_certType) {
|
||||
case Certificate.CERTIFICATE_TYPE_NULL:
|
||||
case Certificate.CERTIFICATE_TYPE_HIDDEN:
|
||||
pkf.setCertType(_certType);
|
||||
break;
|
||||
case Certificate.CERTIFICATE_TYPE_HASHCASH:
|
||||
pkf.setHashCashCert(_hashCashValue);
|
||||
break;
|
||||
case Certificate.CERTIFICATE_TYPE_SIGNED:
|
||||
if (_certSigner == null || _certSigner.trim().length() <= 0)
|
||||
return "No signing destination specified";
|
||||
// find the signer's key file...
|
||||
String signerPKF = null;
|
||||
for (int i = 0; i < getTunnelCount(); i++) {
|
||||
TunnelController c = getController(i);
|
||||
if (_certSigner.equals(c.getConfig("").getProperty("name")) ||
|
||||
_certSigner.equals(c.getConfig("").getProperty("spoofedHost"))) {
|
||||
signerPKF = c.getConfig("").getProperty("privKeyFile");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (signerPKF == null || signerPKF.length() <= 0)
|
||||
return "Signing destination " + _certSigner + " not found";
|
||||
if (_privKeyFile.equals(signerPKF))
|
||||
return "Self-signed destinations not allowed";
|
||||
Certificate c = pkf.setSignedCert(new PrivateKeyFile(signerPKF));
|
||||
if (c == null)
|
||||
return "Signing failed - does signer destination exist?";
|
||||
break;
|
||||
default:
|
||||
return "Unknown certificate type";
|
||||
}
|
||||
Destination newdest;
|
||||
try {
|
||||
pkf.write();
|
||||
newdest = pkf.getDestination();
|
||||
} catch (Exception e) {
|
||||
return "Modification failed: " + e;
|
||||
}
|
||||
return "Destination modified - " +
|
||||
"New Base32 is " + Base32.encode(newdest.calculateHash().getData()) + ".b32.i2p " +
|
||||
"New Destination is " + newdest.toBase64();
|
||||
}
|
||||
|
||||
/** New key */
|
||||
private String generateNewEncryptionKey() {
|
||||
TunnelController tun = getController(_tunnel);
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "Invalid params";
|
||||
if (tun == null) {
|
||||
// creating new
|
||||
tun = new TunnelController(config, "", true);
|
||||
_group.addController(tun);
|
||||
saveChanges();
|
||||
} else if (tun.getIsRunning() || tun.getIsStarting()) {
|
||||
return "Tunnel must be stopped before modifying leaseset encryption key";
|
||||
}
|
||||
byte[] data = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
_context.random().nextBytes(data);
|
||||
SessionKey sk = new SessionKey(data);
|
||||
setEncryptKey(sk.toBase64());
|
||||
setEncrypt("");
|
||||
saveChanges();
|
||||
return "New Leaseset Encryption Key: " + sk.toBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on all provided data, create a set of configuration parameters
|
||||
* suitable for use in a TunnelController. This will replace (not add to)
|
||||
@ -566,82 +770,79 @@ public class IndexBean {
|
||||
Properties config = new Properties();
|
||||
updateConfigGeneric(config);
|
||||
|
||||
if ("httpclient".equals(_type)) {
|
||||
if (isClient(_type)) {
|
||||
// generic client stuff
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
}else if ("ircclient".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
for (String p : _booleanClientOpts)
|
||||
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
|
||||
for (String p : _otherClientOpts)
|
||||
if (_otherOptions.containsKey(p))
|
||||
config.setProperty("option." + p, _otherOptions.get(p));
|
||||
} else {
|
||||
// generic server stuff
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
for (String p : _booleanServerOpts)
|
||||
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
|
||||
for (String p : _otherServerOpts)
|
||||
if (_otherOptions.containsKey(p))
|
||||
config.setProperty("option." + p, _otherOptions.get(p));
|
||||
}
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
} else if ("client".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
} else if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
} else if ("server".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static final String _noShowOpts[] = {
|
||||
"inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance",
|
||||
"inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity",
|
||||
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize"
|
||||
};
|
||||
private static final String _booleanClientOpts[] = {
|
||||
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
|
||||
};
|
||||
private static final String _booleanServerOpts[] = {
|
||||
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", "i2cp.enableAccessList"
|
||||
};
|
||||
private static final String _otherClientOpts[] = {
|
||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime"
|
||||
};
|
||||
private static final String _otherServerOpts[] = {
|
||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList"
|
||||
};
|
||||
protected static final Set _noShowSet = new HashSet();
|
||||
static {
|
||||
_noShowSet.addAll(Arrays.asList(_noShowOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_booleanClientOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
|
||||
}
|
||||
|
||||
private void updateConfigGeneric(Properties config) {
|
||||
config.setProperty("type", _type);
|
||||
if (_name != null)
|
||||
@ -655,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);
|
||||
@ -664,19 +867,9 @@ public class IndexBean {
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
if (_noShowSet.contains(key))
|
||||
continue;
|
||||
String val = pair.substring(eq+1);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.lengthVariance".equals(key)) continue;
|
||||
if ("outbound.lengthVariance".equals(key)) continue;
|
||||
if ("inbound.backupQuantity".equals(key)) continue;
|
||||
if ("outbound.backupQuantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
config.setProperty("option." + key, val);
|
||||
}
|
||||
}
|
||||
@ -704,14 +897,14 @@ public class IndexBean {
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))&& (!"ircclient".equals(_type))) || (!_sharedClient) ) {
|
||||
if ( (!isClient(_type)) || (!_sharedClient) ) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
} else {
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("interactive".equals(_profile))
|
||||
// This was 1 which doesn't make much sense
|
||||
// The real way to make it interactive is to make the streaming lib
|
||||
|
@ -14,12 +14,10 @@ String tun = request.getParameter("tunnel");
|
||||
} else {
|
||||
String type = request.getParameter("type");
|
||||
int curTunnel = -1;
|
||||
if ("client".equals(type) || "httpclient".equals(type) || "ircclient".equals(type)) {
|
||||
if (EditBean.isClient(type)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else if ("server".equals(type) || "httpserver".equals(type)) {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
} else {
|
||||
%>Invalid tunnel type<%
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
}
|
||||
}
|
||||
%>
|
||||
%>
|
||||
|
@ -75,7 +75,11 @@
|
||||
</div>
|
||||
|
||||
<div id="accessField" class="rowItem">
|
||||
<% if ("streamrclient".equals(tunnelType)) { %>
|
||||
<label>Target:</label>
|
||||
<% } else { %>
|
||||
<label>Access Point:</label>
|
||||
<% } %>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="port" accesskey="P">
|
||||
@ -87,14 +91,17 @@
|
||||
</label>
|
||||
<input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<% String otherInterface = "";
|
||||
String clientInterface = editBean.getClientInterface(curTunnel);
|
||||
if ("streamrclient".equals(tunnelType)) {
|
||||
otherInterface = clientInterface;
|
||||
} else { %>
|
||||
<div id="reachField" class="rowItem">
|
||||
<label for="reachableBy" accesskey="r">
|
||||
<span class="accessKey">R</span>eachable by:
|
||||
</label>
|
||||
<select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
|
||||
<% String clientInterface = editBean.getClientInterface(curTunnel);
|
||||
String otherInterface = "";
|
||||
if (!("127.0.0.1".equals(clientInterface)) &&
|
||||
<% if (!("127.0.0.1".equals(clientInterface)) &&
|
||||
!("0.0.0.0".equals(clientInterface)) &&
|
||||
(clientInterface != null) &&
|
||||
(clientInterface.trim().length() > 0)) {
|
||||
@ -105,9 +112,18 @@
|
||||
<option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>>LAN Hosts (Please specify your LAN address)</option>
|
||||
</select>
|
||||
</div>
|
||||
<% } // streamrclient %>
|
||||
<div id="otherField" class="rowItem">
|
||||
<label for="reachableByOther" accesskey="O">
|
||||
<% if ("streamrclient".equals(tunnelType)) { %>
|
||||
Host:
|
||||
<% String vvv = otherInterface;
|
||||
if (vvv == null || "".equals(vvv.trim()))
|
||||
out.write(" <font color=\"red\">(required)</font>");
|
||||
%>
|
||||
<% } else { %>
|
||||
<span class="accessKey">O</span>ther:
|
||||
<% } %>
|
||||
</label>
|
||||
<input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />
|
||||
</div>
|
||||
@ -116,14 +132,14 @@
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) {
|
||||
<% if ("httpclient".equals(tunnelType) || "connectclient".equals(tunnelType)) {
|
||||
%><div id="destinationField" class="rowItem">
|
||||
<label for="proxyList" accesskey="x">
|
||||
Outpro<span class="accessKey">x</span>ies:
|
||||
</label>
|
||||
<input type="text" size="30" id="proxyList" name="proxyList" title="List of Outproxy I2P destinations" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<% } else {
|
||||
<% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType) || "streamrclient".equals(tunnelType)) {
|
||||
%><div id="destinationField" class="rowItem">
|
||||
<label for="targetDestination" accesskey="T">
|
||||
<span class="accessKey">T</span>unnel Destination:
|
||||
@ -135,8 +151,9 @@
|
||||
<input type="text" size="30" id="targetDestination" name="targetDestination" title="Destination of the Tunnel" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
|
||||
<span class="comment">(name or destination)</span>
|
||||
</div>
|
||||
<% }
|
||||
%><div id="profileField" class="rowItem">
|
||||
<% } %>
|
||||
<% if (!"streamrclient".equals(tunnelType)) { %>
|
||||
<div id="profileField" class="rowItem">
|
||||
<label for="profile" accesskey="f">
|
||||
Pro<span class="accessKey">f</span>ile:
|
||||
</label>
|
||||
@ -160,6 +177,7 @@
|
||||
<input value="true" type="checkbox" id="shared" name="shared" title="Share tunnels with other clients"<%=(editBean.isSharedClient(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
<span class="comment">(Share tunnels with other clients and irc/httpclients? Change requires restart of client proxy)</span>
|
||||
</div>
|
||||
<% } // !streamrclient %>
|
||||
<div id="startupField" class="rowItem">
|
||||
<label for="startOnLoad" accesskey="a">
|
||||
<span class="accessKey">A</span>uto Start:
|
||||
@ -205,12 +223,12 @@
|
||||
<span class="accessKey">V</span>ariance:
|
||||
</label>
|
||||
<select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox">
|
||||
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1);
|
||||
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option>
|
||||
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
|
||||
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option>
|
||||
<option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option>
|
||||
<option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option>
|
||||
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
|
||||
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (not recommended)</option>
|
||||
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
|
||||
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
|
||||
<% }
|
||||
@ -265,11 +283,118 @@
|
||||
</label>
|
||||
<input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label for="reduce" accesskey="d">
|
||||
Re<span class="accessKey">d</span>uce tunnel quantity when idle:
|
||||
</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="access" accesskey="d">
|
||||
Enable:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="startOnLoad" name="reduce" title="Reduce Tunnels"<%=(editBean.getReduce(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="reduceCount" accesskey="d">
|
||||
Reduced tunnel count:
|
||||
</label>
|
||||
<input type="text" id="port" name="reduceCount" size="1" maxlength="1" title="Reduced Tunnel Count" value="<%=editBean.getReduceCount(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="reduceTime" accesskey="d">
|
||||
Idle minutes:
|
||||
</label>
|
||||
<input type="text" id="port" name="reduceTime" size="4" maxlength="4" title="Reduced Tunnel Idle Time" value="<%=editBean.getReduceTime(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label for="reduce" accesskey="c">
|
||||
<span class="accessKey">C</span>lose tunnels when idle: <i>Experimental</i>
|
||||
</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="access" accesskey="c">
|
||||
Enable:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="startOnLoad" name="close" title="Close Tunnels"<%=(editBean.getClose(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="access" accesskey="c">
|
||||
New Keys on Reopen:
|
||||
</label>
|
||||
<table border="0"><tr><!-- I give up -->
|
||||
<td><input value="1" type="radio" id="startOnLoad" name="newDest" title="New Destination"
|
||||
<%=(editBean.getNewDest(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
<td valign="center">Enable
|
||||
<td><input value="0" type="radio" id="startOnLoad" name="newDest" title="New Destination"
|
||||
<%=(editBean.getNewDest(curTunnel) || editBean.getPersistentClientKey(curTunnel) ? "" : " checked=\"checked\"")%> class="tickbox" />
|
||||
<td valign="center">Disable
|
||||
</table>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="reduceTime" accesskey="c">
|
||||
Idle minutes:
|
||||
</label>
|
||||
<input type="text" id="port" name="closeTime" size="4" maxlength="4" title="Close Tunnel Idle Time" value="<%=editBean.getCloseTime(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label for="reduce" accesskey="c">
|
||||
<span class="accessKey">D</span>elay tunnel open until required: <i>Experimental</i>
|
||||
</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="access" accesskey="c">
|
||||
Enable:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="startOnLoad" name="delayOpen" title="Delay Tunnel Open"<%=(editBean.getDelayOpen(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { %>
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label for="privKeyFile" accesskey="k">
|
||||
Persistent private <span class="accessKey">k</span>ey:
|
||||
</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>Enable:</label>
|
||||
<input value="2" type="radio" id="startOnLoad" name="newDest" title="New Destination"
|
||||
<%=(editBean.getPersistentClientKey(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
</div>
|
||||
<div id="reachField" class="rowItem">
|
||||
<label>File:</label>
|
||||
<input type="text" size="30" id="clientHost" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="destinationField" class="rowItem">
|
||||
<label for="localDestination" accesskey="L">
|
||||
<span class="accessKey">L</span>ocal destination:
|
||||
</label>
|
||||
<textarea rows="1" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off"><%=editBean.getDestinationBase64(curTunnel)%></textarea>
|
||||
<span class="comment">(if known)</span>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div id="customOptionsField" class="rowItem">
|
||||
<label for="customOptions" accesskey="u">
|
||||
C<span class="accessKey">u</span>stom options:
|
||||
@ -284,8 +409,11 @@
|
||||
<div class="header"></div>
|
||||
<div class="footer">
|
||||
<div class="toolbox">
|
||||
<span class="comment">NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted</span>
|
||||
<input type="hidden" value="true" name="removeConfirm" />
|
||||
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
|
||||
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button>
|
||||
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
|
||||
<button id="controlCancel" class="control" type="submit" name="action" value="" title="Cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user