forked from I2P_Developers/i2p.i2p
Compare commits
217 Commits
i2p_0_6_0_
...
i2p_0_6_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784d465d17 | ||
|
|
327089c9d1 | ||
|
|
148dd99c86 | ||
|
|
98277d3b64 | ||
|
|
702e5a5eab | ||
|
|
54c91731c0 | ||
|
|
3bfc109476 | ||
|
|
3989638f2d | ||
|
|
4a65fd4f46 | ||
|
|
d525c49d45 | ||
|
|
c287bace0f | ||
|
|
ee0951b5b2 | ||
|
|
1eb3ae5e1b | ||
|
|
7d234b1978 | ||
|
|
6f424fa751 | ||
|
|
7726bd1a5c | ||
|
|
2a922098d6 | ||
|
|
3ec92c8b62 | ||
|
|
b37bb9372e | ||
|
|
369b6930e5 | ||
|
|
5033a22a9b | ||
|
|
6e5114a4c2 | ||
|
|
77c818a0b1 | ||
|
|
7ac673cea4 | ||
|
|
e5fa7e0ae4 | ||
|
|
ab4f3008cb | ||
|
|
f738a02760 | ||
|
|
7d64ecb628 | ||
|
|
7beacff028 | ||
|
|
952bcc696a | ||
|
|
19bba048f2 | ||
|
|
5966fcf5ff | ||
|
|
fbd7feee61 | ||
|
|
5faca98176 | ||
|
|
99951bf815 | ||
|
|
024b1a04e2 | ||
|
|
a4cc18df76 | ||
|
|
fef578973a | ||
|
|
35b75a7300 | ||
|
|
1c6c397913 | ||
|
|
c96965d364 | ||
|
|
12900ca709 | ||
|
|
f5b829a124 | ||
|
|
f62a6d3ce6 | ||
|
|
3d18bf870b | ||
|
|
d8071296eb | ||
|
|
c66e3256aa | ||
|
|
686742a67b | ||
|
|
cdf94295f3 | ||
|
|
fbf1705c4e | ||
|
|
453ecc4208 | ||
|
|
d1f2b447ac | ||
|
|
70c4560f02 | ||
|
|
9089fdd2d5 | ||
|
|
ef82cc4f20 | ||
|
|
f2c2a5b386 | ||
|
|
fc858bc950 | ||
|
|
dbb4b3d0c2 | ||
|
|
2b841ad667 | ||
|
|
5e094b43b3 | ||
|
|
33d57dd545 | ||
|
|
61f75b5f09 | ||
|
|
3f65e53592 | ||
|
|
99ae3ee459 | ||
|
|
f7236d7d58 | ||
|
|
5182008b38 | ||
|
|
9d030327e6 | ||
|
|
a15c90d2cc | ||
|
|
84c2a713e1 | ||
|
|
7db9ce6e5b | ||
|
|
da42f5717b | ||
|
|
5241953e5d | ||
|
|
b1a5f61ba2 | ||
|
|
024a5a1ad4 | ||
|
|
b031de5404 | ||
|
|
dceac73951 | ||
|
|
8f95143488 | ||
|
|
a8f3043aae | ||
|
|
6c91b2d4a9 | ||
|
|
ddd438de35 | ||
|
|
16fd46db2b | ||
|
|
1159c155a4 | ||
|
|
30b4e2aa2a | ||
|
|
ae46fa2e6d | ||
|
|
9fc34895c9 | ||
|
|
08be4c7b3d | ||
|
|
7443457af4 | ||
|
|
979a4cfb69 | ||
|
|
ed285871bf | ||
|
|
8c70b8b32a | ||
|
|
14134694d7 | ||
|
|
807d2d3509 | ||
|
|
b222cd43f4 | ||
|
|
7f6aa327f2 | ||
|
|
49564a3878 | ||
|
|
12ddaff0ce | ||
|
|
a8ea239dcc | ||
|
|
ca391097a9 | ||
|
|
4297edc88f | ||
|
|
6de4673e9e | ||
|
|
f6979c811f | ||
|
|
bd86483204 | ||
|
|
53cf03cec6 | ||
|
|
e284a8878b | ||
|
|
14cd469c6d | ||
|
|
9050d7c218 | ||
|
|
0ad18cd0ba | ||
|
|
ca0af146b7 | ||
|
|
a2d2b031f4 | ||
|
|
2f36912ac0 | ||
|
|
53e32c8e64 | ||
|
|
10dde610dc | ||
|
|
f7c2ae9a3b | ||
|
|
ac3b88b9e9 | ||
|
|
60124cdcdc | ||
|
|
e7d2281772 | ||
|
|
52ace2d695 | ||
|
|
b5a25801b4 | ||
|
|
3fc0558810 | ||
|
|
bd9c6ff463 | ||
|
|
a0c822af96 | ||
|
|
4de302101d | ||
|
|
84383c3dab | ||
|
|
a94abb13a4 | ||
|
|
ad59ab691b | ||
|
|
ee9ac31c8b | ||
|
|
788998307a | ||
|
|
2f8a2879bb | ||
|
|
c7b9525d2c | ||
|
|
3fbc6f41af | ||
|
|
6534c84578 | ||
|
|
3816c79193 | ||
|
|
8458e4e0af | ||
|
|
0b9e4967a0 | ||
|
|
05e2da7c22 | ||
|
|
13bda1f6d7 | ||
|
|
ea22c73a73 | ||
|
|
aa5f1cb18d | ||
|
|
ab9c6d59cf | ||
|
|
76655d01d0 | ||
|
|
138f7d3b8d | ||
|
|
df4b998a6a | ||
|
|
2d70103f88 | ||
|
|
731e26e7d6 | ||
|
|
f9d3b157f0 | ||
|
|
12775c416d | ||
|
|
2ca4e63216 | ||
|
|
918d8f851f | ||
|
|
20ea680ff0 | ||
|
|
cabb607211 | ||
|
|
00a4761b5e | ||
|
|
3516701272 | ||
|
|
c4d785667a | ||
|
|
123e0ba589 | ||
|
|
197237aa32 | ||
|
|
f30dc2b480 | ||
|
|
d4ff34eacb | ||
|
|
978769a05d | ||
|
|
993c70f600 | ||
|
|
5dfa9ad7f6 | ||
|
|
f282fe3854 | ||
|
|
9297564555 | ||
|
|
e7ad516685 | ||
|
|
ad574c8504 | ||
|
|
38617fe0a7 | ||
|
|
cdee5b2c31 | ||
|
|
7f6e65c76f | ||
|
|
4dd628dbc8 | ||
|
|
3b5b48ad8a | ||
|
|
c4cac3f3f1 | ||
|
|
4a49e98c31 | ||
|
|
0c0e269e72 | ||
|
|
70b6f97abe | ||
|
|
0013677b83 | ||
|
|
a98ceda64d | ||
|
|
91ea1d0395 | ||
|
|
4aa65c3bb3 | ||
|
|
0a1f59940a | ||
|
|
f540dc798b | ||
|
|
30f6f26a68 | ||
|
|
ea3bf3ffc8 | ||
|
|
831d5ac70c | ||
|
|
1962867ad9 | ||
|
|
6019a03029 | ||
|
|
df5736f571 | ||
|
|
9a73c6defe | ||
|
|
9dfa87ba47 | ||
|
|
934a269753 | ||
|
|
1c0dfc242b | ||
|
|
3bc3e5d47e | ||
|
|
55869af2cc | ||
|
|
9f336dd05b | ||
|
|
411ca5e6c3 | ||
|
|
c528e4db03 | ||
|
|
848ead7683 | ||
|
|
1b8419b9b5 | ||
|
|
900420719e | ||
|
|
ef7d1ba964 | ||
|
|
ab1654c784 | ||
|
|
24bad8e4bb | ||
|
|
f6d8200bc8 | ||
|
|
aef33548b3 | ||
|
|
56ecdcce82 | ||
|
|
b9b59ff95f | ||
|
|
aa9dd3e5c6 | ||
|
|
30bd659149 | ||
|
|
3286ca49c8 | ||
|
|
7700d12178 | ||
|
|
557b7e3f2e | ||
|
|
3e1e9146e1 | ||
|
|
40d8d1aac1 | ||
|
|
1457b8efba | ||
|
|
3821e80ac8 | ||
|
|
d40bb459ea | ||
|
|
edf04f07c9 | ||
|
|
2bdea23986 | ||
|
|
6be0c4b694 |
100
Makefile.gcj
Normal file
100
Makefile.gcj
Normal file
@@ -0,0 +1,100 @@
|
||||
# Makefile for building native I2P binaries and libraries with GCJ
|
||||
#
|
||||
# WARNING: Do not use this yet, as it may explode (etc).
|
||||
#
|
||||
GCJ=gcj #/usr/local/gcc-4.0.2/bin/gcj
|
||||
EXTRA_LD_PATH= #/usr/local/gcc-4.0.2/lib
|
||||
ANT=ant #/opt/apache-ant-1.6.5/bin/ant
|
||||
ANT_TARGET=buildclean
|
||||
NATIVE_DIR=native
|
||||
|
||||
##
|
||||
# Define what jar files get into libi2p.so. The current setup is
|
||||
# *incredibly* lazy, throwing everything in the .so, rather than
|
||||
# give each .jar file its own .so.
|
||||
# i2p.jar: base SDK
|
||||
# mstreaming.jar: streaming API
|
||||
# streaming.jar: full streaming lib implementation
|
||||
# i2ptunnel.jar: I2PTunnel proxy
|
||||
# sam.jar: SAM bridge and API
|
||||
# i2psnark.jar: bittorrent client
|
||||
# router.jar: full I2P router
|
||||
# jbigi.jar: collection of native optimized GMP routines for crypto
|
||||
JAR_BASE=i2p.jar mstreaming.jar streaming.jar
|
||||
JAR_CLIENTS=i2ptunnel.jar sam.jar i2psnark.jar
|
||||
JAR_ROUTER=router.jar
|
||||
JAR_JBIGI=jbigi.jar
|
||||
JAR_XML=xml-apis.jar resolver.jar xercesImpl.jar
|
||||
JAR_CONSOLE=\
|
||||
javax.servlet.jar \
|
||||
commons-el.jar \
|
||||
commons-logging.jar \
|
||||
jasper-runtime.jar \
|
||||
ant-apache-bcel.jar \
|
||||
ant.jar \
|
||||
jasper-compiler.jar \
|
||||
org.mortbay.jetty.jar \
|
||||
routerconsole.jar
|
||||
JAR_SUCKER=jdom.jar rome-0.7.jar sucker.jar
|
||||
LIBI2P_JARS=${JAR_BASE} ${JAR_CLIENTS} ${JAR_ROUTER} ${JAR_JBIGI}
|
||||
# unfortunately, its not quite ready for most end users, as the
|
||||
# ${JAR_CONSOLE} fails to compile with:
|
||||
# org/apache/commons/logging/impl/LogKitLogger.java: In class 'org.apache.commons.logging.impl.LogKitLogger':
|
||||
# .../LogKitLogger.java: In constructor '(java.lang.String)':
|
||||
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:104: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:104: confused by earlier errors, bailing out
|
||||
|
||||
#${JAR_CONSOLE}\
|
||||
#${JAR_XML} \
|
||||
#${JAR_SUCKER}
|
||||
#${JAR_CONSOLE}
|
||||
|
||||
SYSTEM_PROPS=-DloggerFilenameOverride=logs/log-router-@.txt \
|
||||
-Dorg.mortbay.http.Version.paranoid=true \
|
||||
-Dorg.mortbay.util.FileResource.checkAliases=false \
|
||||
-Dorg.mortbay.xml.XmlParser.NotValidating=true
|
||||
#SYSTEM_PROPS=-Di2p.weakPRNG=true
|
||||
OPTIMIZE=-O2
|
||||
#OPTIMIZE=-O3
|
||||
|
||||
LD_LIBRARY_PATH=${EXTRA_LD_PATH}:.
|
||||
|
||||
all: jars native
|
||||
@echo "* Build complete"
|
||||
|
||||
jars:
|
||||
@${ANT} ${ANT_TARGET}
|
||||
|
||||
clean: native_clean
|
||||
|
||||
native: native_clean native_shared
|
||||
@echo "* Native code build in ${NATIVE}"
|
||||
|
||||
native_clean:
|
||||
@rm -rf ${NATIVE_DIR}
|
||||
@mkdir ${NATIVE_DIR}
|
||||
|
||||
native_shared: libi2p.so
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
|
||||
@echo "* i2p_dsa is a simple test app with the DSA engine and Fortuna PRNG to make sure crypto is working"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.Fortuna
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
|
||||
@echo "* i2ptunnel is mihi's I2PTunnel CLI"
|
||||
@echo " run it as ./i2ptunnel -cli to avoid awt complaints"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
|
||||
@echo "* i2ptunnelctl is a controller for I2PTunnel, reading i2ptunnel.config"
|
||||
@echo " and launching the appropriate proxies"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
@echo "* i2psnark is an anonymous bittorrent client"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
|
||||
@echo "* i2prouter is the main I2P router"
|
||||
@echo " it can be used, and while the router console won't load,"
|
||||
@echo " i2ptunnel will, so it will start all the proxies defined in i2ptunnel.config"
|
||||
|
||||
libi2p.so:
|
||||
@echo "* Building libi2p.so"
|
||||
@(cd build ; ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@ls -l ${NATIVE_DIR}/libi2p.so
|
||||
@echo "* libi2p.so built"
|
||||
@@ -143,7 +143,7 @@ public class Daemon {
|
||||
defaultSettings.put("subscriptions", "subscriptions.txt");
|
||||
defaultSettings.put("etags", "etags");
|
||||
defaultSettings.put("last_modified", "last_modified");
|
||||
defaultSettings.put("update_delay", "1");
|
||||
defaultSettings.put("update_delay", "12");
|
||||
|
||||
File homeFile = new File(home);
|
||||
if (!homeFile.exists()) {
|
||||
@@ -188,4 +188,4 @@ public class Daemon {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
340
apps/i2psnark/COPYING
Normal file
340
apps/i2psnark/COPYING
Normal file
@@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. 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.
|
||||
|
||||
12. 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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
24
apps/i2psnark/TODO
Normal file
24
apps/i2psnark/TODO
Normal file
@@ -0,0 +1,24 @@
|
||||
- I2PSnark:
|
||||
- add multitorrent support by checking the metainfo hash in the
|
||||
PeerAcceptor and feeding it off to the appropriate coordinator
|
||||
- add a web interface
|
||||
|
||||
- BEncode
|
||||
- Byte array length indicator can overflow.
|
||||
- Support really big BigNums (only 256 chars allowed now)
|
||||
- Better BEValue toString(). Uses stupid heuristic now for debugging.
|
||||
- Implemented bencoding.
|
||||
- Remove application level hack to calculate sha1 hash for metainfo
|
||||
(But can it be done as efficiently?)
|
||||
|
||||
- Storage
|
||||
- Check file name filter.
|
||||
|
||||
- TrackerClient
|
||||
- Support undocumented &numwant= request.
|
||||
|
||||
- PeerCoordinator
|
||||
- Disconnect from other seeds as soon as you are a seed yourself.
|
||||
|
||||
- Text UI
|
||||
- Make it completely silent.
|
||||
1
apps/i2psnark/authors.snark
Normal file
1
apps/i2psnark/authors.snark
Normal file
@@ -0,0 +1 @@
|
||||
Mark Wielaard <mark@klomp.org>
|
||||
487
apps/i2psnark/changelog.snark
Normal file
487
apps/i2psnark/changelog.snark
Normal file
@@ -0,0 +1,487 @@
|
||||
2003-06-27 14:24 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Update version number and explain new features.
|
||||
|
||||
2003-06-27 13:51 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/GnomeInfoWindow.java,
|
||||
org/klomp/snark/GnomePeerList.java,
|
||||
org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/SnarkGnome.java: Add GnomeInfoWindow.
|
||||
|
||||
2003-06-27 00:37 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Implement 'info' and 'list' commands.
|
||||
|
||||
2003-06-27 00:05 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/GnomePeerList.java,
|
||||
org/klomp/snark/SnarkGnome.java: Add GnomePeerList to show state of
|
||||
connected peers.
|
||||
|
||||
2003-06-27 00:04 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerID.java: Make Comparable.
|
||||
|
||||
2003-06-23 23:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerMonitorTask.java: Correctly update
|
||||
lastDownloaded and lastUploaded.
|
||||
|
||||
2003-06-23 23:20 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: When checking storage use the
|
||||
MetaInfo from the storage.
|
||||
|
||||
2003-06-23 21:47 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Storage.java: Fill piece hashes, not info hashes.
|
||||
|
||||
2003-06-23 21:42 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/MetaInfo.java: New package private
|
||||
getPieceHashes() method.
|
||||
|
||||
2003-06-22 19:49 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README, TODO, org/klomp/snark/Snark.java: Add new command line
|
||||
switch --no-commands. Don't read interactive commands or show
|
||||
usage info.
|
||||
|
||||
2003-06-22 19:26 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/PeerCheckerTask.java,
|
||||
org/klomp/snark/PeerMonitorTask.java, org/klomp/snark/Snark.java:
|
||||
Split peer statistic reporting from PeerCheckerTask into
|
||||
PeerMonitorTask. Use new task in Snark text ui.
|
||||
|
||||
2003-06-22 18:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Only print peer id when debug level
|
||||
is INFO or higher.
|
||||
|
||||
2003-06-22 18:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/ShutdownListener.java: Add new ShutdownListener
|
||||
interface.
|
||||
|
||||
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: Text UI item to not read from stdin.
|
||||
|
||||
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* snark-gnome.sh: kaffe java-gnome support (but crashes hard at the
|
||||
moment).
|
||||
|
||||
2003-06-22 14:04 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/CoordinatorListener.java,
|
||||
org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/ProgressListener.java, org/klomp/snark/Snark.java,
|
||||
org/klomp/snark/SnarkGnome.java,
|
||||
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
|
||||
org/klomp/snark/StorageListener.java: Split ProgressListener into
|
||||
Storage, Coordinator and Shutdown listener.
|
||||
|
||||
2003-06-20 19:06 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCoordinator.java, Snark.java,
|
||||
SnarkGnome.java, Storage.java: Progress listeners for both Storage
|
||||
and PeerCoordinator.
|
||||
|
||||
2003-06-20 14:50 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/ProgressListener.java,
|
||||
org/klomp/snark/SnarkGnome.java: Add ProgressListener.
|
||||
|
||||
2003-06-20 13:22 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/SnarkGnome.java: Add Pieces collected field.
|
||||
|
||||
2003-06-20 12:26 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCoordinator.java, PeerListener.java,
|
||||
PeerState.java: Add PeerListener.downloaded() which gets called on
|
||||
chunk updates. Keep PeerCoordinator.downloaded up to date using
|
||||
this remove adjusting in gotPiece() except when we receive a bad
|
||||
piece.
|
||||
|
||||
2003-06-16 00:27 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, snark-gnome.sh, org/klomp/snark/Snark.java,
|
||||
org/klomp/snark/SnarkGnome.java: Start of a Gnome GUI.
|
||||
|
||||
2003-06-05 13:19 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: Don't remove a BAD piece
|
||||
from the wantedPieces list. Revert to synchronizing on
|
||||
wantedPieces for all relevant sections.
|
||||
|
||||
2003-06-03 21:09 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Only call readLine() when !quit.
|
||||
Always print exception when fatal() is called.
|
||||
|
||||
2003-06-01 23:12 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Set release version to 0.4.
|
||||
|
||||
2003-06-01 22:59 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionIn.java: Handle negative length
|
||||
prefixes (terminates connection).
|
||||
|
||||
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Snark.java, SnarkShutdown.java: Implement
|
||||
correct shutdown and read commands from stdin.
|
||||
|
||||
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerInfo.java: Check that interval and peers
|
||||
list actually exist.
|
||||
|
||||
2003-06-01 21:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Storage.java: Implement close().
|
||||
|
||||
2003-06-01 21:05 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Fix debug logging.
|
||||
|
||||
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: Implement halt().
|
||||
|
||||
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/ConnectionAcceptor.java: Rename stop() to halt().
|
||||
|
||||
2003-06-01 17:35 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Drop lock on this when calling
|
||||
addRequest() from havePiece().
|
||||
|
||||
2003-06-01 14:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README, org/klomp/snark/ConnectionAcceptor.java,
|
||||
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
|
||||
org/klomp/snark/PeerCheckerTask.java,
|
||||
org/klomp/snark/PeerConnectionIn.java,
|
||||
org/klomp/snark/PeerConnectionOut.java,
|
||||
org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/PeerState.java, org/klomp/snark/Snark.java,
|
||||
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
|
||||
org/klomp/snark/Tracker.java, org/klomp/snark/TrackerClient.java:
|
||||
Add debug/log level.
|
||||
|
||||
2003-05-31 23:04 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java: Use
|
||||
just one lock (peers) for all synchronization (even for
|
||||
wantedPieces). Let PeerChecker handle real disconnect and keep
|
||||
count of uploaders.
|
||||
|
||||
2003-05-31 22:29 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerConnectionIn.java: Set state to
|
||||
null on first disconnect() call. So always check whether it might
|
||||
already be null. Helps disconnect check.
|
||||
|
||||
2003-05-31 22:27 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Don't explicitly close
|
||||
the DataOutputStream (if another thread is using it libgcj seems to
|
||||
not like it very much).
|
||||
|
||||
2003-05-30 21:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Cancel
|
||||
(un)interested/(un)choke when (inverse) is still in send queue.
|
||||
Remove pieces from send queue when choke message is actaully send.
|
||||
|
||||
2003-05-30 19:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Make sure listener.wantPiece(int)
|
||||
is never called while lock on this is held.
|
||||
|
||||
2003-05-30 19:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Indentation cleanup.
|
||||
|
||||
2003-05-30 17:50 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Storage.java: Only synchronize on bitfield as
|
||||
long as necessary.
|
||||
|
||||
2003-05-30 17:43 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Tracker.java: Identing cleanup.
|
||||
|
||||
2003-05-30 16:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Better error message.
|
||||
|
||||
2003-05-30 15:11 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Make sure not to hold the lock on
|
||||
this when calling the listener to prevent deadlocks. Implement
|
||||
handling and sending of cancel messages.
|
||||
|
||||
2003-05-30 14:50 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: First check if we still
|
||||
want a piece before trying to add it to the Storage.
|
||||
|
||||
2003-05-30 14:49 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Implement
|
||||
sendCancel(Request). Add cancelRequest(int, int, int).
|
||||
|
||||
2003-05-30 14:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Request.java: Add hashCode() and equals(Object)
|
||||
methods.
|
||||
|
||||
2003-05-30 14:45 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Peer.java: Fix wheter -> whether javadoc
|
||||
comments. Mark state null immediatly after calling
|
||||
listener.disconnected(). Call PeerState.havePiece() not
|
||||
PeerConnectionOut.sendHave() directly.
|
||||
|
||||
2003-05-25 19:23 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: Add PeerCoordinator TODO for connecting to seeds.
|
||||
|
||||
2003-05-23 12:12 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile: Create class files with jikes again.
|
||||
|
||||
2003-05-18 22:01 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
|
||||
Prefer to (optimistically) unchoke first those peers that unchoked
|
||||
us. And make sure to not unchoke a peer that we just choked.
|
||||
|
||||
2003-05-18 21:48 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Peer.java: Fix isChoked() to not always return
|
||||
true.
|
||||
|
||||
2003-05-18 14:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
|
||||
PeerCoordinator.java, PeerState.java: Remove separate Peer
|
||||
downloading/uploading states. Keep choke and interest always up to
|
||||
date. Uploading is now just when we are not choking the peer.
|
||||
Downloading is now defined as being unchoked and interesting.
|
||||
CHECK_PERIOD is now 20 seconds. MAX_CONNECTIONS is now 24.
|
||||
MAX_DOWNLOADERS doesn't exists anymore. We download whenever we can
|
||||
from peers.
|
||||
|
||||
2003-05-18 13:57 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Remove piece messages
|
||||
from queue when we are choking. (They will have to be rerequested
|
||||
when we unchoke the peer again.)
|
||||
|
||||
2003-05-15 00:08 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Ignore missed chunk requests,
|
||||
don't requeue them.
|
||||
|
||||
2003-05-15 00:06 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Request.java: Add sanity check
|
||||
|
||||
2003-05-10 15:47 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Add extra '(' to usage message.
|
||||
|
||||
2003-05-10 15:22 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Set version to 0.3 (The Bakers Tale).
|
||||
|
||||
2003-05-10 15:17 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Mention received piece in warning
|
||||
message.
|
||||
|
||||
2003-05-10 03:20 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerConnectionIn.java, PeerState.java,
|
||||
Request.java: Remove currentRequest and handle all piece messages
|
||||
from the lastRequested list.
|
||||
|
||||
2003-05-09 20:02 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Fix nothing requested warning
|
||||
message.
|
||||
|
||||
2003-05-09 19:59 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Piece messages are big.
|
||||
So if there are other (control) messages make sure they are send
|
||||
first. Also remove request messages from the queue if we are
|
||||
currently being choked to prevent them from being send even if we
|
||||
get unchoked a little later. (Since we will resent them anyway in
|
||||
that case.)
|
||||
|
||||
2003-05-09 18:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
|
||||
PeerCoordinator.java, PeerID.java: New definition of PeerID.equals
|
||||
(port + address + id) and new method PeerID.sameID (only id). These
|
||||
are used to really see if we already have a connection to a certain
|
||||
peer (active setup vs passive setup).
|
||||
|
||||
2003-05-08 03:05 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Use Snark.debug() not
|
||||
System.out.println().
|
||||
|
||||
2003-05-06 20:29 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: s/noting/nothing/
|
||||
|
||||
2003-05-06 20:28 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile: s/lagacy/legacy/
|
||||
|
||||
2003-05-05 23:17 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Set version to 0.2, explain new functionality and add
|
||||
examples.
|
||||
|
||||
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* .cvsignore, Makefile, org/klomp/snark/StaticSnark.java: Enable
|
||||
-static binary creation.
|
||||
|
||||
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Tracker.java: Disable --ip support.
|
||||
|
||||
2003-05-05 21:02 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: HttpAcceptor.java, PeerCheckerTask.java,
|
||||
PeerCoordinator.java, TrackerClient.java: Use Snark.debug() not
|
||||
System.out.println().
|
||||
|
||||
2003-05-05 21:01 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionIn.java: Be prepared to handle the
|
||||
case where currentRequest is null.
|
||||
|
||||
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Improve argument parsing errors.
|
||||
|
||||
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile: Use gcj -C again for creating the class files.
|
||||
|
||||
2003-05-05 09:24 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Just clear outstandingRequests,
|
||||
never make it null.
|
||||
|
||||
2003-05-05 02:55 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Always retry both first
|
||||
started event and every other event as long the TrackerClient is
|
||||
not stopped.
|
||||
|
||||
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Remove double assignment port.
|
||||
|
||||
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: Add Tracker TODO item.
|
||||
|
||||
2003-05-04 23:38 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: ConnectionAcceptor.java, MetaInfo.java,
|
||||
Snark.java, Storage.java, Tracker.java: Add info hash calcultation
|
||||
to MetaInfo. Add torrent creation to Storage. Add ip parameter
|
||||
handling to Tracker. Make ConnectionAcceptor handle
|
||||
null/non-existing HttpAcceptors. Add debug output, --ip handling
|
||||
and all the above to Snark.
|
||||
|
||||
2003-05-04 23:36 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Handle all failing requests
|
||||
the same (print a warning).
|
||||
|
||||
2003-05-03 15:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerID.java, TrackerInfo.java: Split
|
||||
Peer and PeerID a little more.
|
||||
|
||||
2003-05-03 15:44 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/MetaInfo.java: Add reannounce() and
|
||||
getTorrentData().
|
||||
|
||||
2003-05-03 15:38 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
|
||||
More concise verbose/debug output. Always use addUpDownloader() to
|
||||
set peers upload or download state to true.
|
||||
|
||||
2003-05-03 13:38 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Compile fixes.
|
||||
|
||||
2003-05-03 13:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Only generate fatal() call on
|
||||
first Tracker access. Otherwise just print a warning error message.
|
||||
|
||||
2003-05-03 03:10 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Better handle resending
|
||||
outstanding pieces and try to recover better from unrequested
|
||||
pieces.
|
||||
|
||||
2003-05-02 21:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/HttpAcceptor.java,
|
||||
org/klomp/snark/MetaInfo.java, org/klomp/snark/PeerID.java,
|
||||
org/klomp/snark/Snark.java, org/klomp/snark/Tracker.java,
|
||||
org/klomp/snark/TrackerClient.java,
|
||||
org/klomp/snark/bencode/BEncoder.java: Add Tracker, PeerID and
|
||||
BEncoder.
|
||||
|
||||
2003-05-01 20:17 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/ConnectionAcceptor.java,
|
||||
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
|
||||
org/klomp/snark/PeerAcceptor.java, org/klomp/snark/Snark.java: Add
|
||||
ConnectionAcceptor that handles both PeerAcceptor and HttpAcceptor.
|
||||
|
||||
2003-05-01 18:39 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: connected() synchronize on
|
||||
peers.
|
||||
|
||||
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/SnarkShutdown.java: Wait some time before
|
||||
returning...
|
||||
|
||||
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: More items.
|
||||
|
||||
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Calculate real random ID.
|
||||
|
||||
2003-04-27 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* snark: Initial (0.1) version.
|
||||
93
apps/i2psnark/java/build.xml
Normal file
93
apps/i2psnark/java/build.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2psnark">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar, war" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../jetty/" target="build" />
|
||||
<ant dir="../../streaming/java/" target="build" />
|
||||
<!-- streaming will build ministreaming and core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="war" depends="jar">
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml">
|
||||
<classes dir="./build/obj" includes="**/*" />
|
||||
</war>
|
||||
</target>
|
||||
|
||||
<target name="standalone" depends="standalone_prep">
|
||||
<zip destfile="i2psnark-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="i2psnark/" />
|
||||
</zip>
|
||||
</target>
|
||||
<target name="standalone_prep" depends="war">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build" srcdir="src/" includes="org/klomp/snark/web/RunStandalone.java" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
|
||||
<jar destfile="./build/launch-i2psnark.jar" basedir="./build/" includes="org/klomp/snark/web/RunStandalone.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.web.RunStandalone" />
|
||||
<attribute name="Class-Path" value="lib/i2p.jar lib/mstreaming.jar lib/streaming.jar lib/commons-el.jar lib/commons-logging.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/javax.servlet.jar lib/org.mortbay.jetty.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
|
||||
<delete dir="./dist" />
|
||||
<mkdir dir="./dist" />
|
||||
<copy file="./build/launch-i2psnark.jar" tofile="./dist/launch-i2psnark.jar" />
|
||||
<mkdir dir="./dist/webapps" />
|
||||
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
|
||||
<mkdir dir="./dist/lib" />
|
||||
<copy file="../../../core/java/build/i2p.jar" tofile="./dist/lib/i2p.jar" />
|
||||
<copy file="../../jetty/jettylib/commons-el.jar" tofile="./dist/lib/commons-el.jar" />
|
||||
<copy file="../../jetty/jettylib/commons-logging.jar" tofile="./dist/lib/commons-logging.jar" />
|
||||
<copy file="../../jetty/jettylib/javax.servlet.jar" tofile="./dist/lib/javax.servlet.jar" />
|
||||
<copy file="../../jetty/jettylib/org.mortbay.jetty.jar" tofile="./dist/lib/org.mortbay.jetty.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-compiler.jar" tofile="./dist/lib/jasper-compiler.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-runtime.jar" tofile="./dist/lib/jasper-runtime.jar" />
|
||||
<copy file="../../ministreaming/java/build/mstreaming.jar" tofile="./dist/lib/mstreaming.jar" />
|
||||
<copy file="../../streaming/java/build/streaming.jar" tofile="./dist/lib/streaming.jar" />
|
||||
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
|
||||
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
|
||||
<mkdir dir="./dist/work" />
|
||||
<mkdir dir="./dist/logs" />
|
||||
|
||||
<zip destfile="i2psnark-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="i2psnark/" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
<delete file="../i2psnark.war" />
|
||||
<delete file="./i2psnark-standalone.zip" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
131
apps/i2psnark/java/src/org/klomp/snark/BitField.java
Normal file
131
apps/i2psnark/java/src/org/klomp/snark/BitField.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/* BitField - Container of a byte array representing set and unset bits.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Container of a byte array representing set and unset bits.
|
||||
*/
|
||||
public class BitField
|
||||
{
|
||||
|
||||
private final byte[] bitfield;
|
||||
private final int size;
|
||||
|
||||
/**
|
||||
* Creates a new BitField that represents <code>size</code> unset bits.
|
||||
*/
|
||||
public BitField(int size)
|
||||
{
|
||||
this.size = size;
|
||||
int arraysize = ((size-1)/8)+1;
|
||||
bitfield = new byte[arraysize];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new BitField that represents <code>size</code> bits
|
||||
* as set by the given byte array. This will make a copy of the array.
|
||||
* Extra bytes will be ignored.
|
||||
*
|
||||
* @exception ArrayOutOfBoundsException if give byte array is not large
|
||||
* enough.
|
||||
*/
|
||||
public BitField(byte[] bitfield, int size)
|
||||
{
|
||||
this.size = size;
|
||||
int arraysize = ((size-1)/8)+1;
|
||||
this.bitfield = new byte[arraysize];
|
||||
|
||||
// XXX - More correct would be to check that unused bits are
|
||||
// cleared or clear them explicitly ourselves.
|
||||
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the actual byte array used. Changes to this array
|
||||
* effect this BitField. Note that some bits at the end of the byte
|
||||
* array are supposed to be always unset if they represent bits
|
||||
* bigger then the size of the bitfield.
|
||||
*/
|
||||
public byte[] getFieldBytes()
|
||||
{
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the BitField. The returned value is one bigger
|
||||
* then the last valid bit number (since bit numbers are counted
|
||||
* from zero).
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to true.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
*/
|
||||
public void set(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
bitfield[index] |= mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bit is set or false if it is not.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
*/
|
||||
public boolean get(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
return (bitfield[index] & mask) != 0;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
// Not very efficient
|
||||
StringBuffer sb = new StringBuffer("BitField[");
|
||||
for (int i = 0; i < size; i++)
|
||||
if (get(i))
|
||||
{
|
||||
sb.append(' ');
|
||||
sb.append(i);
|
||||
}
|
||||
sb.append(" ]");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
182
apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
Normal file
182
apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
Normal file
@@ -0,0 +1,182 @@
|
||||
/* ConnectionAcceptor - Accepts connections and routes them to sub-acceptors.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
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.Log;
|
||||
|
||||
/**
|
||||
* Accepts connections on a TCP port and routes them to sub-acceptors.
|
||||
*/
|
||||
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 boolean stop;
|
||||
private boolean socketChanged;
|
||||
|
||||
private ConnectionAcceptor() {}
|
||||
|
||||
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
|
||||
if (serverSocket != socket) {
|
||||
if ( (peeracceptor == null) || (peeracceptor.coordinators != set) )
|
||||
peeracceptor = new PeerAcceptor(set);
|
||||
serverSocket = socket;
|
||||
stop = false;
|
||||
socketChanged = true;
|
||||
if (thread == null) {
|
||||
thread = new I2PThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionAcceptor(I2PServerSocket serverSocket,
|
||||
PeerAcceptor peeracceptor)
|
||||
{
|
||||
this.serverSocket = serverSocket;
|
||||
this.peeracceptor = peeracceptor;
|
||||
|
||||
socketChanged = false;
|
||||
stop = false;
|
||||
thread = new I2PThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void halt()
|
||||
{
|
||||
if (true) throw new RuntimeException("wtf");
|
||||
stop = true;
|
||||
|
||||
I2PServerSocket ss = serverSocket;
|
||||
if (ss != null)
|
||||
try
|
||||
{
|
||||
ss.close();
|
||||
}
|
||||
catch(I2PException ioe) { }
|
||||
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
serverSocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
socketChanged = true;
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return 6881; // serverSocket.getLocalPort();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
while(!stop)
|
||||
{
|
||||
if (socketChanged) {
|
||||
// ok, already updated
|
||||
socketChanged = false;
|
||||
}
|
||||
if (serverSocket == null) {
|
||||
Snark.debug("Server socket went away.. boo hiss", Snark.ERROR);
|
||||
stop = true;
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
I2PSocket socket = serverSocket.accept();
|
||||
if (socket == null) {
|
||||
if (socketChanged) {
|
||||
continue;
|
||||
} else {
|
||||
Snark.debug("Null socket accepted, but socket wasn't changed?", Snark.ERROR);
|
||||
}
|
||||
} else {
|
||||
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
catch (I2PException ioe)
|
||||
{
|
||||
if (!socketChanged) {
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (I2PException ignored) { }
|
||||
|
||||
throw new RuntimeException("wtf");
|
||||
}
|
||||
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _socket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_socket = socket;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = _socket.getInputStream();
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
|
||||
if (true) {
|
||||
in = new BufferedInputStream(in);
|
||||
//out = new BufferedOutputStream(out);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
|
||||
peeracceptor.connection(_socket, in, out);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash().toBase64(), ioe);
|
||||
try { _socket.close(); } catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* CoordinatorListener.java - Callback when a peer changes state
|
||||
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback used when some peer changes state.
|
||||
*/
|
||||
public interface CoordinatorListener
|
||||
{
|
||||
/**
|
||||
* Called when the PeerCoordinator notices a change in the state of a peer.
|
||||
*/
|
||||
void peerChange(PeerCoordinator coordinator, Peer peer);
|
||||
}
|
||||
231
apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
Normal file
231
apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
Normal file
@@ -0,0 +1,231 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* I2P specific helpers for I2PSnark
|
||||
*/
|
||||
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;
|
||||
private int _proxyPort;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private Map _opts;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_opts = new HashMap();
|
||||
setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_configured = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify what HTTP proxy tracker requests should go through (specify a null
|
||||
* host for no proxying)
|
||||
*
|
||||
*/
|
||||
public void setProxy(String host, int port) {
|
||||
if ( (host != null) && (port > 0) ) {
|
||||
_shouldProxy = true;
|
||||
_proxyHost = host;
|
||||
_proxyPort = port;
|
||||
} else {
|
||||
_shouldProxy = false;
|
||||
_proxyHost = null;
|
||||
_proxyPort = -1;
|
||||
}
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
if (opts != null)
|
||||
_opts.putAll(opts);
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
public String getEepProxyHost() { return _proxyHost; }
|
||||
public int getEepProxyPort() { return _proxyPort; }
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
*/
|
||||
public boolean connect() {
|
||||
if (_manager == null) {
|
||||
Properties opts = new Properties();
|
||||
if (_opts != null) {
|
||||
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
opts.setProperty(key, _opts.get(key).toString());
|
||||
}
|
||||
}
|
||||
if (opts.getProperty("inbound.nickname") == null)
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "90000");
|
||||
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityAction", "1");
|
||||
if (opts.getProperty("i2p.streaming.writeTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.writeTimeout", "90000");
|
||||
if (opts.getProperty("i2p.streaming.readTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.readTimeout", "90000");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
}
|
||||
return (_manager != null);
|
||||
}
|
||||
|
||||
public boolean connected() { return _manager != null; }
|
||||
/**
|
||||
* Destroy the destination itself
|
||||
*/
|
||||
public void disconnect() {
|
||||
I2PSocketManager mgr = _manager;
|
||||
_manager = null;
|
||||
mgr.destroySocketManager();
|
||||
}
|
||||
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
try {
|
||||
return _manager.connect(peer.getAddress());
|
||||
} catch (I2PException ie) {
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the given URL, returning the file it is stored in, or null on error
|
||||
*/
|
||||
public File get(String url) { return get(url, true); }
|
||||
public File get(String url, boolean rewrite) {
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
File out = null;
|
||||
try {
|
||||
out = File.createTempFile("i2psnark", "url");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
String fetchURL = url;
|
||||
if (rewrite)
|
||||
fetchURL = rewriteAnnounce(url);
|
||||
//_log.debug("Rewritten url [" + fetchURL + "]");
|
||||
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), fetchURL);
|
||||
if (get.fetch()) {
|
||||
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
|
||||
return out;
|
||||
} else {
|
||||
_log.warn("Fetch failed [" + url + "]");
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
return _manager.getServerSocket();
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
I2PSession sess = _manager.getSession();
|
||||
if (sess != null) {
|
||||
Destination dest = sess.getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
Destination getDestination(String ip) {
|
||||
if (ip == null) return null;
|
||||
if (ip.endsWith(".i2p")) {
|
||||
Destination dest = _context.namingService().lookup(ip);
|
||||
if (dest != null) {
|
||||
return dest;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given http://blah.i2p/foo/announce turn it into http://i2p/blah/foo/announce
|
||||
*/
|
||||
String rewriteAnnounce(String origAnnounce) {
|
||||
int destStart = "http://".length();
|
||||
int destEnd = origAnnounce.indexOf(".i2p");
|
||||
int pathStart = origAnnounce.indexOf('/', destEnd);
|
||||
String rv = "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
|
||||
//_log.debug("Rewriting [" + origAnnounce + "] as [" + rv + "]");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** hook between snark's logger and an i2p log */
|
||||
void debug(String msg, int snarkDebugLevel, Throwable t) {
|
||||
if (t instanceof OutOfMemoryError) {
|
||||
try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
try {
|
||||
t.printStackTrace();
|
||||
} catch (Throwable tt) {}
|
||||
try {
|
||||
System.out.println("OOM thread: " + Thread.currentThread().getName());
|
||||
} catch (Throwable tt) {}
|
||||
}
|
||||
switch (snarkDebugLevel) {
|
||||
case 0:
|
||||
case 1:
|
||||
_log.error(msg, t);
|
||||
break;
|
||||
case 2:
|
||||
_log.warn(msg, t);
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
_log.info(msg, t);
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
default:
|
||||
_log.debug(msg, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
apps/i2psnark/java/src/org/klomp/snark/Message.java
Normal file
141
apps/i2psnark/java/src/org/klomp/snark/Message.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/* Message - A protocol message which can be send through a DataOutputStream.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
// Used to queue outgoing connections
|
||||
// sendMessage() should be used to translate them to wire format.
|
||||
class Message
|
||||
{
|
||||
final static byte KEEP_ALIVE = -1;
|
||||
final static byte CHOKE = 0;
|
||||
final static byte UNCHOKE = 1;
|
||||
final static byte INTERESTED = 2;
|
||||
final static byte UNINTERESTED = 3;
|
||||
final static byte HAVE = 4;
|
||||
final static byte BITFIELD = 5;
|
||||
final static byte REQUEST = 6;
|
||||
final static byte PIECE = 7;
|
||||
final static byte CANCEL = 8;
|
||||
|
||||
// Not all fields are used for every message.
|
||||
// KEEP_ALIVE doesn't have a real wire representation
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
// Used for PIECE and BITFIELD messages
|
||||
byte[] data;
|
||||
int off;
|
||||
int len;
|
||||
|
||||
SimpleTimer.TimedEvent expireEvent;
|
||||
|
||||
/** Utility method for sending a message through a DataStream. */
|
||||
void sendMessage(DataOutputStream dos) throws IOException
|
||||
{
|
||||
// KEEP_ALIVE is special.
|
||||
if (type == KEEP_ALIVE)
|
||||
{
|
||||
dos.writeInt(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the total length in bytes
|
||||
|
||||
// Type is one byte.
|
||||
int datalen = 1;
|
||||
|
||||
// piece is 4 bytes.
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// begin/offset is 4 bytes
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// length is 4 bytes
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// add length of data for piece or bitfield array.
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
datalen += len;
|
||||
|
||||
// Send length
|
||||
dos.writeInt(datalen);
|
||||
dos.writeByte(type & 0xFF);
|
||||
|
||||
// Send additional info (piece number)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
dos.writeInt(piece);
|
||||
|
||||
// Send additional info (begin/offset)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
dos.writeInt(begin);
|
||||
|
||||
// Send additional info (length); for PIECE this is implicit.
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
dos.writeInt(length);
|
||||
|
||||
// Send actual data
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
dos.write(data, off, len);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case KEEP_ALIVE:
|
||||
return "KEEP_ALIVE";
|
||||
case CHOKE:
|
||||
return "CHOKE";
|
||||
case UNCHOKE:
|
||||
return "UNCHOKE";
|
||||
case INTERESTED:
|
||||
return "INTERESTED";
|
||||
case UNINTERESTED:
|
||||
return "UNINTERESTED";
|
||||
case HAVE:
|
||||
return "HAVE(" + piece + ")";
|
||||
case BITFIELD:
|
||||
return "BITFIELD";
|
||||
case REQUEST:
|
||||
return "REQUEST(" + piece + "," + begin + "," + length + ")";
|
||||
case PIECE:
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
}
|
||||
}
|
||||
}
|
||||
461
apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
Normal file
461
apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
Normal file
@@ -0,0 +1,461 @@
|
||||
/* MetaInfo - Holds all information gotten from a torrent file.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.crypto.SHA1;
|
||||
|
||||
/**
|
||||
* Note: this class is buggy, as it doesn't propogate custom meta fields into the bencoded
|
||||
* info data, and from there to the info_hash. At the moment, though, it seems to work with
|
||||
* torrents created by I2P-BT, I2PRufus and Azureus.
|
||||
*
|
||||
*/
|
||||
public class MetaInfo
|
||||
{
|
||||
private static final Log _log = new Log(MetaInfo.class);
|
||||
private final String announce;
|
||||
private final byte[] info_hash;
|
||||
private final String name;
|
||||
private final String name_utf8;
|
||||
private final List files;
|
||||
private final List files_utf8;
|
||||
private final List lengths;
|
||||
private final int piece_length;
|
||||
private final byte[] piece_hashes;
|
||||
private final long length;
|
||||
private final Map infoMap;
|
||||
|
||||
private byte[] torrentdata;
|
||||
|
||||
MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
|
||||
int piece_length, byte[] piece_hashes, long length)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
this.name_utf8 = name_utf8;
|
||||
this.files = files;
|
||||
this.files_utf8 = null;
|
||||
this.lengths = lengths;
|
||||
this.piece_length = piece_length;
|
||||
this.piece_hashes = piece_hashes;
|
||||
this.length = length;
|
||||
|
||||
this.info_hash = calculateInfoHash();
|
||||
infoMap = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MetaInfo from the given InputStream. The
|
||||
* InputStream must start with a correctly bencoded dictonary
|
||||
* describing the torrent.
|
||||
*/
|
||||
public MetaInfo(InputStream in) throws IOException
|
||||
{
|
||||
this(new BDecoder(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MetaInfo from the given BDecoder. The BDecoder
|
||||
* must have a complete dictionary describing the torrent.
|
||||
*/
|
||||
public MetaInfo(BDecoder be) throws IOException
|
||||
{
|
||||
// Note that evaluation order matters here...
|
||||
this(be.bdecodeMap().getMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MetaInfo from a Map of BEValues and the SHA1 over
|
||||
* the original bencoded info dictonary (this is a hack, we could
|
||||
* reconstruct the bencoded stream and recalculate the hash). Will
|
||||
* throw a InvalidBEncodingException if the given map does not
|
||||
* contain a valid announce string or info dictonary.
|
||||
*/
|
||||
public MetaInfo(Map m) throws InvalidBEncodingException
|
||||
{
|
||||
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
|
||||
BEValue val = (BEValue)m.get("announce");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing announce string");
|
||||
this.announce = val.getString();
|
||||
|
||||
val = (BEValue)m.get("info");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing info map");
|
||||
Map info = val.getMap();
|
||||
infoMap = info;
|
||||
|
||||
val = (BEValue)info.get("name");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing name string");
|
||||
name = val.getString();
|
||||
|
||||
val = (BEValue)info.get("name.utf-8");
|
||||
if (val != null)
|
||||
name_utf8 = val.getString();
|
||||
else
|
||||
name_utf8 = null;
|
||||
|
||||
val = (BEValue)info.get("piece length");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing piece length number");
|
||||
piece_length = val.getInt();
|
||||
|
||||
val = (BEValue)info.get("pieces");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing piece bytes");
|
||||
piece_hashes = val.getBytes();
|
||||
|
||||
val = (BEValue)info.get("length");
|
||||
if (val != null)
|
||||
{
|
||||
// Single file case.
|
||||
length = val.getLong();
|
||||
files = null;
|
||||
files_utf8 = null;
|
||||
lengths = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multi file case.
|
||||
val = (BEValue)info.get("files");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException
|
||||
("Missing length number and/or files list");
|
||||
|
||||
List list = val.getList();
|
||||
int size = list.size();
|
||||
if (size == 0)
|
||||
throw new InvalidBEncodingException("zero size files list");
|
||||
|
||||
files = new ArrayList(size);
|
||||
files_utf8 = new ArrayList(size);
|
||||
lengths = new ArrayList(size);
|
||||
long l = 0;
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
{
|
||||
Map desc = ((BEValue)list.get(i)).getMap();
|
||||
val = (BEValue)desc.get("length");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing length number");
|
||||
long len = val.getLong();
|
||||
lengths.add(new Long(len));
|
||||
l += len;
|
||||
|
||||
val = (BEValue)desc.get("path");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing path list");
|
||||
List path_list = val.getList();
|
||||
int path_length = path_list.size();
|
||||
if (path_length == 0)
|
||||
throw new InvalidBEncodingException("zero size file path list");
|
||||
|
||||
List file = new ArrayList(path_length);
|
||||
Iterator it = path_list.iterator();
|
||||
while (it.hasNext())
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
|
||||
files.add(file);
|
||||
|
||||
val = (BEValue)desc.get("path.utf-8");
|
||||
if (val != null) {
|
||||
path_list = val.getList();
|
||||
path_length = path_list.size();
|
||||
if (path_length > 0) {
|
||||
file = new ArrayList(path_length);
|
||||
it = path_list.iterator();
|
||||
while (it.hasNext())
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
files_utf8.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
length = l;
|
||||
}
|
||||
|
||||
info_hash = calculateInfoHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representing the URL of the tracker for this torrent.
|
||||
*/
|
||||
public String getAnnounce()
|
||||
{
|
||||
return announce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original 20 byte SHA1 hash over the bencoded info map.
|
||||
*/
|
||||
public byte[] getInfoHash()
|
||||
{
|
||||
// XXX - Should we return a clone, just to be sure?
|
||||
return info_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the piece hashes. Only used by storage so package local.
|
||||
*/
|
||||
byte[] getPieceHashes()
|
||||
{
|
||||
return piece_hashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested name for the file or toplevel directory.
|
||||
* If it is a toplevel directory name getFiles() will return a
|
||||
* non-null List of file name hierarchy name.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of lists of file name hierarchies or null if it is
|
||||
* a single name. It has the same size as the list returned by
|
||||
* getLengths().
|
||||
*/
|
||||
public List getFiles()
|
||||
{
|
||||
// XXX - Immutable?
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Longs indication the size of the individual
|
||||
* files, or null if it is a single file. It has the same size as
|
||||
* the list returned by getFiles().
|
||||
*/
|
||||
public List getLengths()
|
||||
{
|
||||
// XXX - Immutable?
|
||||
return lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of pieces.
|
||||
*/
|
||||
public int getPieces()
|
||||
{
|
||||
return piece_hashes.length/20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of a piece. All pieces are of equal length
|
||||
* except for the last one (<code>getPieces()-1</code>).
|
||||
*
|
||||
* @exception IndexOutOfBoundsException when piece is equal to or
|
||||
* greater then the number of pieces in the torrent.
|
||||
*/
|
||||
public int getPieceLength(int piece)
|
||||
{
|
||||
int pieces = getPieces();
|
||||
if (piece >= 0 && piece < pieces -1)
|
||||
return piece_length;
|
||||
else if (piece == pieces -1)
|
||||
return (int)(length - piece * piece_length);
|
||||
else
|
||||
throw new IndexOutOfBoundsException("no piece: " + piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given piece has the same SHA1 hash as the given
|
||||
* byte array. Returns random results or IndexOutOfBoundsExceptions
|
||||
* when the piece number is unknown.
|
||||
*/
|
||||
public boolean checkPiece(int piece, byte[] bs, int off, int length)
|
||||
{
|
||||
if (true)
|
||||
return fast_checkPiece(piece, bs, off, length);
|
||||
else
|
||||
return orig_checkPiece(piece, bs, off, length);
|
||||
}
|
||||
private boolean orig_checkPiece(int piece, byte[] bs, int off, int length) {
|
||||
// Check digest
|
||||
MessageDigest sha1;
|
||||
try
|
||||
{
|
||||
sha1 = MessageDigest.getInstance("SHA");
|
||||
}
|
||||
catch (NoSuchAlgorithmException nsae)
|
||||
{
|
||||
throw new InternalError("No SHA digest available: " + nsae);
|
||||
}
|
||||
|
||||
sha1.update(bs, off, length);
|
||||
byte[] hash = sha1.digest();
|
||||
for (int i = 0; i < 20; i++)
|
||||
if (hash[i] != piece_hashes[20 * piece + i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean fast_checkPiece(int piece, byte[] bs, int off, int length) {
|
||||
SHA1 sha1 = new SHA1();
|
||||
|
||||
sha1.update(bs, off, length);
|
||||
byte[] hash = sha1.digest();
|
||||
for (int i = 0; i < 20; i++)
|
||||
if (hash[i] != piece_hashes[20 * piece + i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length of the torrent in bytes.
|
||||
*/
|
||||
public long getTotalLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "MetaInfo[info_hash='" + hexencode(info_hash)
|
||||
+ "', announce='" + announce
|
||||
+ "', name='" + name
|
||||
+ "', files=" + files
|
||||
+ ", #pieces='" + piece_hashes.length/20
|
||||
+ "', piece_length='" + piece_length
|
||||
+ "', length='" + length
|
||||
+ "']";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a byte array as a hex encoded string.
|
||||
*/
|
||||
private static String hexencode(byte[] bs)
|
||||
{
|
||||
StringBuffer sb = new StringBuffer(bs.length*2);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
{
|
||||
int c = bs[i] & 0xFF;
|
||||
if (c < 16)
|
||||
sb.append('0');
|
||||
sb.append(Integer.toHexString(c));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this MetaInfo that shares everything except the
|
||||
* announce URL.
|
||||
*/
|
||||
public MetaInfo reannounce(String announce)
|
||||
{
|
||||
return new MetaInfo(announce, name, name_utf8, files,
|
||||
lengths, piece_length,
|
||||
piece_hashes, length);
|
||||
}
|
||||
|
||||
public byte[] getTorrentData()
|
||||
{
|
||||
if (torrentdata == null)
|
||||
{
|
||||
Map m = new HashMap();
|
||||
m.put("announce", announce);
|
||||
Map info = createInfoMap();
|
||||
m.put("info", info);
|
||||
torrentdata = BEncoder.bencode(m);
|
||||
}
|
||||
return torrentdata;
|
||||
}
|
||||
|
||||
private Map createInfoMap()
|
||||
{
|
||||
Map info = new HashMap();
|
||||
if (infoMap != null) {
|
||||
info.putAll(infoMap);
|
||||
return info;
|
||||
}
|
||||
info.put("name", name);
|
||||
if (name_utf8 != null)
|
||||
info.put("name.utf-8", name_utf8);
|
||||
info.put("piece length", new Integer(piece_length));
|
||||
info.put("pieces", piece_hashes);
|
||||
if (files == null)
|
||||
info.put("length", new Long(length));
|
||||
else
|
||||
{
|
||||
List l = new ArrayList();
|
||||
for (int i = 0; i < files.size(); i++)
|
||||
{
|
||||
Map file = new HashMap();
|
||||
file.put("path", files.get(i));
|
||||
if ( (files_utf8 != null) && (files_utf8.size() > i) )
|
||||
file.put("path.utf-8", files_utf8.get(i));
|
||||
file.put("length", lengths.get(i));
|
||||
l.add(file);
|
||||
}
|
||||
info.put("files", l);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private byte[] calculateInfoHash()
|
||||
{
|
||||
Map info = createInfoMap();
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("info: ");
|
||||
for (Iterator iter = info.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
Object val = info.get(key);
|
||||
buf.append(key).append('=');
|
||||
if (val instanceof byte[])
|
||||
buf.append(Base64.encode((byte[])val, true));
|
||||
else
|
||||
buf.append(val.toString());
|
||||
}
|
||||
_log.debug(buf.toString());
|
||||
byte[] infoBytes = BEncoder.bencode(info);
|
||||
//_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA");
|
||||
byte hash[] = digest.digest(infoBytes);
|
||||
_log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]");
|
||||
return hash;
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
throw new InternalError(nsa.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
461
apps/i2psnark/java/src/org/klomp/snark/Peer.java
Normal file
461
apps/i2psnark/java/src/org/klomp/snark/Peer.java
Normal file
@@ -0,0 +1,461 @@
|
||||
/* Peer - All public information concerning a peer.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class Peer implements Comparable
|
||||
{
|
||||
private Log _log = new Log(Peer.class);
|
||||
// Identifying property, the peer id of the other side.
|
||||
private final PeerID peerID;
|
||||
|
||||
private final byte[] my_id;
|
||||
final MetaInfo metainfo;
|
||||
|
||||
// The data in/output streams set during the handshake and used by
|
||||
// the actual connections.
|
||||
private DataInputStream din;
|
||||
private DataOutputStream dout;
|
||||
|
||||
// Keeps state for in/out connections. Non-null when the handshake
|
||||
// was successful, the connection setup and runs
|
||||
PeerState state;
|
||||
|
||||
private I2PSocket sock;
|
||||
|
||||
private boolean deregister = true;
|
||||
private static long __id;
|
||||
private long _id;
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
* relevant MetaInfo.
|
||||
*/
|
||||
public Peer(PeerID peerID, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this.peerID = peerID;
|
||||
this.my_id = my_id;
|
||||
this.metainfo = metainfo;
|
||||
_id = ++__id;
|
||||
//_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unconnected peer from the input and output stream got
|
||||
* from the socket. Note that the complete handshake (which can take
|
||||
* some time or block indefinitely) is done in the calling Thread to
|
||||
* get the remote peer id. To completely start the connection call
|
||||
* the connect() method.
|
||||
*
|
||||
* @exception IOException when an error occurred during the handshake.
|
||||
*/
|
||||
public Peer(final I2PSocket sock, InputStream in, OutputStream out, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this.my_id = my_id;
|
||||
this.metainfo = metainfo;
|
||||
this.sock = sock;
|
||||
|
||||
byte[] id = handshake(in, out);
|
||||
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||
_id = ++__id;
|
||||
_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the peer.
|
||||
*/
|
||||
public PeerID getPeerID()
|
||||
{
|
||||
return peerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String representation of the peerID.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
if (peerID != null)
|
||||
return peerID.toString() + ' ' + _id;
|
||||
else
|
||||
return "[unknown id] " + _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code of a Peer is the hash code of the peerID.
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return peerID.hashCode() ^ (2 << _id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Two Peers are equal when they have the same PeerID.
|
||||
* All other properties are ignored.
|
||||
*/
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o instanceof Peer)
|
||||
{
|
||||
Peer p = (Peer)o;
|
||||
return _id == p._id && peerID.equals(p.peerID);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the PeerIDs.
|
||||
*/
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
Peer p = (Peer)o;
|
||||
int rv = peerID.compareTo(p.peerID);
|
||||
if (rv == 0) {
|
||||
if (_id > p._id) return 1;
|
||||
else if (_id < p._id) return -1;
|
||||
else return 0;
|
||||
} else {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the connection to the other peer. This method does not
|
||||
* return until the connection is terminated.
|
||||
*
|
||||
* When the connection is correctly started the connected() method
|
||||
* of the given PeerListener is called. If the connection ends or
|
||||
* the connection could not be setup correctly the disconnected()
|
||||
* method is called.
|
||||
*
|
||||
* If the given BitField is non-null it is send to the peer as first
|
||||
* message.
|
||||
*/
|
||||
public void runConnection(PeerListener listener, BitField bitfield)
|
||||
{
|
||||
if (state != null)
|
||||
throw new IllegalStateException("Peer already started");
|
||||
|
||||
_log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting"));
|
||||
try
|
||||
{
|
||||
// Do we need to handshake?
|
||||
if (din == null)
|
||||
{
|
||||
sock = I2PSnarkUtil.instance().connect(peerID);
|
||||
_log.debug("Connected to " + peerID + ": " + sock);
|
||||
if ((sock == null) || (sock.isClosed())) {
|
||||
throw new IOException("Unable to reach " + peerID);
|
||||
}
|
||||
InputStream in = sock.getInputStream();
|
||||
OutputStream out = sock.getOutputStream(); //new BufferedOutputStream(sock.getOutputStream());
|
||||
if (true) {
|
||||
// buffered output streams are internally synchronized, so we can't get through to the underlying
|
||||
// I2PSocket's MessageOutputStream to close() it if we are blocking on a write(...). Oh, and the
|
||||
// buffer is unnecessary anyway, as unbuffered access lets the streaming lib do the 'right thing'.
|
||||
//out = new BufferedOutputStream(out);
|
||||
in = new BufferedInputStream(sock.getInputStream());
|
||||
}
|
||||
//BufferedInputStream bis
|
||||
// = new BufferedInputStream(sock.getInputStream());
|
||||
//BufferedOutputStream bos
|
||||
// = new BufferedOutputStream(sock.getOutputStream());
|
||||
byte [] id = handshake(in, out); //handshake(bis, bos);
|
||||
byte [] expected_id = peerID.getID();
|
||||
if (!Arrays.equals(expected_id, id))
|
||||
throw new IOException("Unexpected peerID '"
|
||||
+ PeerID.idencode(id)
|
||||
+ "' expected '"
|
||||
+ PeerID.idencode(expected_id) + "'");
|
||||
_log.debug("Handshake got matching IDs with " + toString());
|
||||
} else {
|
||||
_log.debug("Already have din [" + sock + "] with " + toString());
|
||||
}
|
||||
|
||||
PeerConnectionIn in = new PeerConnectionIn(this, din);
|
||||
PeerConnectionOut out = new PeerConnectionOut(this, dout);
|
||||
PeerState s = new PeerState(this, listener, metainfo, in, out);
|
||||
|
||||
// Send our bitmap
|
||||
if (bitfield != null)
|
||||
s.out.sendBitfield(bitfield);
|
||||
|
||||
// We are up and running!
|
||||
state = s;
|
||||
listener.connected(this);
|
||||
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
// Use this thread for running the incomming connection.
|
||||
// The outgoing connection creates its own Thread.
|
||||
out.startup();
|
||||
Thread.currentThread().setName("Snark reader from " + peerID);
|
||||
s.in.run();
|
||||
}
|
||||
catch(IOException eofe)
|
||||
{
|
||||
// Ignore, probably just the other side closing the connection.
|
||||
// Or refusing the connection, timing out, etc.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(this.toString(), eofe);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
_log.error(this + ": " + t.getMessage(), t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (deregister) listener.disconnected(this);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets DataIn/OutputStreams, does the handshake and returns the id
|
||||
* reported by the other side.
|
||||
*/
|
||||
private byte[] handshake(InputStream in, OutputStream out) //BufferedInputStream bis, BufferedOutputStream bos)
|
||||
throws IOException
|
||||
{
|
||||
din = new DataInputStream(in);
|
||||
dout = new DataOutputStream(out);
|
||||
|
||||
// Handshake write - header
|
||||
dout.write(19);
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - zeros
|
||||
byte[] zeros = new byte[8];
|
||||
dout.write(zeros);
|
||||
// Handshake write - metainfo hash
|
||||
byte[] shared_hash = metainfo.getInfoHash();
|
||||
dout.write(shared_hash);
|
||||
// Handshake write - peer id
|
||||
dout.write(my_id);
|
||||
dout.flush();
|
||||
|
||||
_log.debug("Wrote my shared hash and ID to " + toString());
|
||||
|
||||
// Handshake read - header
|
||||
byte b = din.readByte();
|
||||
if (b != 19)
|
||||
throw new IOException("Handshake failure, expected 19, got "
|
||||
+ (b & 0xff) + " on " + sock);
|
||||
|
||||
byte[] bs = new byte[19];
|
||||
din.readFully(bs);
|
||||
String bittorrentProtocol = new String(bs, "UTF-8");
|
||||
if (!"BitTorrent protocol".equals(bittorrentProtocol))
|
||||
throw new IOException("Handshake failure, expected "
|
||||
+ "'Bittorrent protocol', got '"
|
||||
+ bittorrentProtocol + "'");
|
||||
|
||||
// Handshake read - zeros
|
||||
din.readFully(zeros);
|
||||
|
||||
// Handshake read - metainfo hash
|
||||
bs = new byte[20];
|
||||
din.readFully(bs);
|
||||
if (!Arrays.equals(shared_hash, bs))
|
||||
throw new IOException("Unexpected MetaInfo hash");
|
||||
|
||||
// Handshake read - peer id
|
||||
din.readFully(bs);
|
||||
_log.debug("Read the remote side's hash and peerID fully from " + toString());
|
||||
return bs;
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
return state != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects this peer if it was connected. If deregister is
|
||||
* true, PeerListener.disconnected() will be called when the
|
||||
* connection is completely terminated. Otherwise the connection is
|
||||
* silently terminated.
|
||||
*/
|
||||
public void disconnect(boolean deregister)
|
||||
{
|
||||
// Both in and out connection will call this.
|
||||
this.deregister = deregister;
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
state = null;
|
||||
|
||||
PeerConnectionIn in = s.in;
|
||||
if (in != null)
|
||||
in.disconnect();
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
out.disconnect();
|
||||
PeerListener pl = s.listener;
|
||||
if (pl != null)
|
||||
pl.disconnected(this);
|
||||
}
|
||||
I2PSocket csock = sock;
|
||||
sock = null;
|
||||
if ( (csock != null) && (!csock.isClosed()) ) {
|
||||
try {
|
||||
csock.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error disconnecting " + toString(), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the peer we have another piece.
|
||||
*/
|
||||
public void have(int piece)
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.havePiece(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer is interested in pieces we have. Returns
|
||||
* false if not connected.
|
||||
*/
|
||||
public boolean isInterested()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) && s.interested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not we are interested in pieces from this peer.
|
||||
* Defaults to false. When interest is true and this peer unchokes
|
||||
* us then we start downloading from it. Has no effect when not connected.
|
||||
*/
|
||||
public void setInteresting(boolean interest)
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.setInteresting(interest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer has pieces we want from it. Returns false
|
||||
* if not connected.
|
||||
*/
|
||||
public boolean isInteresting()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) && s.interesting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not we are choking the peer. Defaults to
|
||||
* true. When choke is false and the peer requests some pieces we
|
||||
* upload them, otherwise requests of this peer are ignored.
|
||||
*/
|
||||
public void setChoking(boolean choke)
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.setChoking(choke);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we are choking the peer. Returns true when not connected.
|
||||
*/
|
||||
public boolean isChoking()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s == null) || s.choking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer choked us. Returns true when not connected.
|
||||
*/
|
||||
public boolean isChoked()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s == null) || s.choked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been downloaded.
|
||||
* Can be reset to zero with <code>resetCounters()</code>/
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) ? s.downloaded : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been uploaded.
|
||||
* Can be reset to zero with <code>resetCounters()</code>/
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) ? s.uploaded : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the downloaded and uploaded counters to zero.
|
||||
*/
|
||||
public void resetCounters()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
s.downloaded = 0;
|
||||
s.uploaded = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public long getInactiveTime() {
|
||||
PeerState s = state;
|
||||
if (s != null) {
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
return System.currentTimeMillis() - out.lastSent;
|
||||
else
|
||||
return -1; //"state, no out";
|
||||
} else {
|
||||
return -1; //"no state";
|
||||
}
|
||||
}
|
||||
}
|
||||
145
apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java
Normal file
145
apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/* PeerAcceptor - Accepts incomming connections from peers.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Accepts incomming connections from peers. The ConnectionAcceptor
|
||||
* will call the connection() method when it detects an incomming BT
|
||||
* protocol connection. The PeerAcceptor will then create a new peer
|
||||
* if the PeerCoordinator wants more peers.
|
||||
*/
|
||||
public class PeerAcceptor
|
||||
{
|
||||
private static final Log _log = new Log(PeerAcceptor.class);
|
||||
private final PeerCoordinator coordinator;
|
||||
final PeerCoordinatorSet coordinators;
|
||||
|
||||
public PeerAcceptor(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
this.coordinators = null;
|
||||
}
|
||||
|
||||
public PeerAcceptor(PeerCoordinatorSet coordinators)
|
||||
{
|
||||
this.coordinators = coordinators;
|
||||
this.coordinator = null;
|
||||
}
|
||||
|
||||
public void connection(I2PSocket socket,
|
||||
InputStream in, OutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
// inside this Peer constructor's handshake is where you'd deal with the other
|
||||
// side saying they want to communicate with another torrent - aka multitorrent
|
||||
// support, but because of how the protocol works, we can get away with just reading
|
||||
// ahead the first $LOOKAHEAD_SIZE bytes to figure out which infohash they want to
|
||||
// talk about, and we can just look for that in our list of active torrents.
|
||||
byte peerInfoHash[] = null;
|
||||
if (in instanceof BufferedInputStream) {
|
||||
in.mark(LOOKAHEAD_SIZE);
|
||||
peerInfoHash = readHash(in);
|
||||
in.reset();
|
||||
} else {
|
||||
// is this working right?
|
||||
try {
|
||||
peerInfoHash = readHash(in);
|
||||
_log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64()
|
||||
+ ": " + Base64.encode(peerInfoHash));
|
||||
} catch (IOException ioe) {
|
||||
_log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
|
||||
throw ioe;
|
||||
}
|
||||
in = new SequenceInputStream(new ByteArrayInputStream(peerInfoHash), in);
|
||||
}
|
||||
if (coordinator != null) {
|
||||
// single torrent capability
|
||||
MetaInfo meta = coordinator.getMetaInfo();
|
||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
||||
if (coordinator.needPeers())
|
||||
{
|
||||
Peer peer = new Peer(socket, in, out, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
coordinator.addPeer(peer);
|
||||
}
|
||||
else
|
||||
socket.close();
|
||||
} else {
|
||||
// its for another infohash, but we are only single torrent capable. b0rk.
|
||||
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
|
||||
+ ") while we only support (" + Base64.encode(meta.getInfoHash()) + ")");
|
||||
}
|
||||
} else {
|
||||
// multitorrent capable, so lets see what we can handle
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator cur = (PeerCoordinator)iter.next();
|
||||
MetaInfo meta = cur.getMetaInfo();
|
||||
|
||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
||||
if (cur.needPeers())
|
||||
{
|
||||
Peer peer = new Peer(socket, in, out, cur.getID(),
|
||||
cur.getMetaInfo());
|
||||
cur.addPeer(peer);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Rejecting new peer for " + cur.snark.torrent);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is only reached if none of the coordinators match the infohash
|
||||
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
|
||||
+ ") while we don't support that hash");
|
||||
}
|
||||
}
|
||||
|
||||
private static final int LOOKAHEAD_SIZE = "19".length() +
|
||||
"BitTorrent protocol".length() +
|
||||
8 + // blank, reserved
|
||||
20; // infohash
|
||||
|
||||
/**
|
||||
* Read ahead to the infohash, throwing an exception if there isn't enough data
|
||||
*/
|
||||
private byte[] readHash(InputStream in) throws IOException {
|
||||
byte buf[] = new byte[LOOKAHEAD_SIZE];
|
||||
int read = DataHelper.read(in, buf);
|
||||
if (read != buf.length)
|
||||
throw new IOException("Unable to read the hash (read " + read + ")");
|
||||
byte rv[] = new byte[20];
|
||||
System.arraycopy(buf, buf.length-rv.length-1, rv, 0, rv.length);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
204
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
204
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
@@ -0,0 +1,204 @@
|
||||
/* PeerCheckTasks - TimerTask that checks for good/bad up/downloaders.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TimerTask that checks for good/bad up/downloader. Works together
|
||||
* with the PeerCoordinator to select which Peers get (un)choked.
|
||||
*/
|
||||
class PeerCheckerTask extends TimerTask
|
||||
{
|
||||
private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
|
||||
|
||||
private final PeerCoordinator coordinator;
|
||||
|
||||
PeerCheckerTask(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
// Calculate total uploading and worst downloader.
|
||||
long worstdownload = Long.MAX_VALUE;
|
||||
Peer worstDownloader = null;
|
||||
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
|
||||
long uploaded = 0;
|
||||
long downloaded = 0;
|
||||
|
||||
// Keep track of peers we remove now,
|
||||
// we will add them back to the end of the list.
|
||||
List removed = new ArrayList();
|
||||
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
|
||||
// Remove dying peers
|
||||
if (!peer.isConnected())
|
||||
{
|
||||
it.remove();
|
||||
coordinator.removePeerFromPieces(peer);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
peers++;
|
||||
|
||||
if (!peer.isChoking())
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
|
||||
// XXX - We should calculate the up/download rate a bit
|
||||
// more intelligently
|
||||
long upload = peer.getUploaded();
|
||||
uploaded += upload;
|
||||
long download = peer.getDownloaded();
|
||||
downloaded += download;
|
||||
peer.resetCounters();
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
{
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
}
|
||||
|
||||
// If we are at our max uploaders and we have lots of other
|
||||
// interested peers try to make some room.
|
||||
// (Note use of coordinator.uploaders)
|
||||
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
&& !peer.isChoking())
|
||||
{
|
||||
// Check if it still wants pieces from us.
|
||||
if (!peer.isInterested())
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isChoked())
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isInteresting()
|
||||
&& !peer.isChoked()
|
||||
&& download == 0)
|
||||
{
|
||||
// We are downloading but didn't receive anything...
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isChoking() && download < worstdownload)
|
||||
{
|
||||
// Make sure download is good if we are uploading
|
||||
worstdownload = download;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resync actual uploaders value
|
||||
// (can shift a bit by disconnecting peers)
|
||||
coordinator.uploaders = uploaders;
|
||||
|
||||
// Remove the worst downloader if needed.
|
||||
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
&& worstDownloader != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
|
||||
worstDownloader.setChoking(true);
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
removed.add(worstDownloader);
|
||||
}
|
||||
|
||||
// Optimistically unchoke a peer
|
||||
coordinator.unchokePeer();
|
||||
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
}
|
||||
if (coordinator.halted()) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
186
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
Normal file
186
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
Normal file
@@ -0,0 +1,186 @@
|
||||
/* PeerConnectionIn - Handles incomming messages and hands them to PeerState.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerConnectionIn implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionIn.class);
|
||||
private final Peer peer;
|
||||
private final DataInputStream din;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
|
||||
public PeerConnectionIn(Peer peer, DataInputStream din)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.din = din;
|
||||
quit = false;
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
if (quit == true)
|
||||
return;
|
||||
|
||||
quit = true;
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
thread = Thread.currentThread();
|
||||
try
|
||||
{
|
||||
PeerState ps = peer.state;
|
||||
while (!quit && ps != null)
|
||||
{
|
||||
// Common variables used for some messages.
|
||||
int piece;
|
||||
int begin;
|
||||
int len;
|
||||
|
||||
// Wait till we hear something...
|
||||
// The length of a complete message in bytes.
|
||||
int i = din.readInt();
|
||||
if (i < 0)
|
||||
throw new IOException("Unexpected length prefix: " + i);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
ps.keepAliveMessage();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received keepalive from " + peer + " on " + peer.metainfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
byte b = din.readByte();
|
||||
Message m = new Message();
|
||||
m.type = b;
|
||||
switch (b)
|
||||
{
|
||||
case 0:
|
||||
ps.chokeMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received choke from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 1:
|
||||
ps.chokeMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unchoke from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 2:
|
||||
ps.interestedMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received interested from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 3:
|
||||
ps.interestedMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received not interested from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 4:
|
||||
piece = din.readInt();
|
||||
ps.haveMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received havePiece(" + piece + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 5:
|
||||
byte[] bitmap = new byte[i-1];
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 6:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.requestMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received request(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 7:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = i-9;
|
||||
Request req = ps.getOutstandingRequest(piece, begin, len);
|
||||
byte[] piece_bytes;
|
||||
if (req != null)
|
||||
{
|
||||
piece_bytes = req.bs;
|
||||
din.readFully(piece_bytes, begin, len);
|
||||
ps.pieceMessage(req);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX - Consume but throw away afterwards.
|
||||
piece_bytes = new byte[len];
|
||||
din.readFully(piece_bytes);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.cancelMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
ps.unknownMessage(b, bs);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unknown message from " + peer + " on " + peer.metainfo.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Ignore, probably the other side closed connection.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("IOError talking with " + peer, ioe);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
_log.error("Error talking with " + peer, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
399
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
Normal file
399
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
Normal file
@@ -0,0 +1,399 @@
|
||||
/* PeerConnectionOut - Keeps a queue of outgoing messages and delivers them.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionOut.class);
|
||||
private final Peer peer;
|
||||
private final DataOutputStream dout;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private List sendQueue = new ArrayList();
|
||||
|
||||
private static long __id = 0;
|
||||
private long _id;
|
||||
|
||||
long lastSent;
|
||||
|
||||
public PeerConnectionOut(Peer peer, DataOutputStream dout)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.dout = dout;
|
||||
_id = ++__id;
|
||||
|
||||
lastSent = System.currentTimeMillis();
|
||||
quit = false;
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
thread = new I2PThread(this, "Snark sender " + _id + ": " + peer);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuesly monitors for more outgoing messages that have to be send.
|
||||
* Stops if quit is true of an IOException occurs.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!quit && peer.isConnected())
|
||||
{
|
||||
Message m = null;
|
||||
PeerState state = null;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
while (!quit && peer.isConnected() && sendQueue.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make sure everything will reach the other side.
|
||||
dout.flush();
|
||||
|
||||
// Wait till more data arrives.
|
||||
sendQueue.wait(60*1000);
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
state = peer.state;
|
||||
if (!quit && state != null && peer.isConnected())
|
||||
{
|
||||
// Piece messages are big. So if there are other
|
||||
// (control) messages make sure they are send first.
|
||||
// Also remove request messages from the queue if
|
||||
// we are currently being choked to prevent them from
|
||||
// being send even if we get unchoked a little later.
|
||||
// (Since we will resent them anyway in that case.)
|
||||
// And remove piece messages if we are choking.
|
||||
|
||||
// this should get fixed for starvation
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (m == null && it.hasNext())
|
||||
{
|
||||
Message nm = (Message)it.next();
|
||||
if (nm.type == Message.PIECE)
|
||||
{
|
||||
if (state.choking) {
|
||||
it.remove();
|
||||
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
}
|
||||
nm = null;
|
||||
}
|
||||
else if (nm.type == Message.REQUEST && state.choked)
|
||||
{
|
||||
it.remove();
|
||||
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
nm = null;
|
||||
}
|
||||
|
||||
if (m == null && nm != null)
|
||||
{
|
||||
m = nm;
|
||||
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && sendQueue.size() > 0) {
|
||||
m = (Message)sendQueue.remove(0);
|
||||
SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m != null)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
|
||||
m.sendMessage(dout);
|
||||
lastSent = System.currentTimeMillis();
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
if (m.type == Message.CHOKE)
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
// XXX - Should also register overhead...
|
||||
if (m.type == Message.PIECE)
|
||||
state.uploaded(m.len);
|
||||
|
||||
m = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Ignore, probably other side closed connection.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("IOError sending to " + peer, ioe);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
_log.error("Error sending to " + peer, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
quit = true;
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect()
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
//if (quit == true)
|
||||
// return;
|
||||
|
||||
quit = true;
|
||||
if (thread != null)
|
||||
thread.interrupt();
|
||||
|
||||
sendQueue.clear();
|
||||
sendQueue.notify();
|
||||
}
|
||||
if (dout != null) {
|
||||
try {
|
||||
dout.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream to " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the sendQueue and notifies the method waiting
|
||||
* on the sendQueue to change.
|
||||
*/
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/** remove messages not sent in 3m */
|
||||
private static final int SEND_TIMEOUT = 3*60*1000;
|
||||
private class RemoveTooSlow implements SimpleTimer.TimedEvent {
|
||||
private Message _m;
|
||||
public RemoveTooSlow(Message m) {
|
||||
_m = m;
|
||||
m.expireEvent = RemoveTooSlow.this;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
boolean removed = false;
|
||||
synchronized (sendQueue) {
|
||||
removed = sendQueue.remove(_m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
if (removed)
|
||||
_log.info("Took too long to send " + _m + " to " + peer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a particular message type from the queue.
|
||||
*
|
||||
* @param type the Message type to remove.
|
||||
* @returns true when a message of the given type was removed, false
|
||||
* otherwise.
|
||||
*/
|
||||
private boolean removeMessage(int type)
|
||||
{
|
||||
boolean removed = false;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == type)
|
||||
{
|
||||
it.remove();
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void sendAlive()
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.KEEP_ALIVE;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendChoke(boolean choke)
|
||||
{
|
||||
// We cancel the (un)choke but keep PIECE messages.
|
||||
// PIECE messages are purged if a choke is actually send.
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
int inverseType = choke ? Message.UNCHOKE
|
||||
: Message.CHOKE;
|
||||
if (!removeMessage(inverseType))
|
||||
{
|
||||
Message m = new Message();
|
||||
if (choke)
|
||||
m.type = Message.CHOKE;
|
||||
else
|
||||
m.type = Message.UNCHOKE;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendInterest(boolean interest)
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
int inverseType = interest ? Message.UNINTERESTED
|
||||
: Message.INTERESTED;
|
||||
if (!removeMessage(inverseType))
|
||||
{
|
||||
Message m = new Message();
|
||||
if (interest)
|
||||
m.type = Message.INTERESTED;
|
||||
else
|
||||
m.type = Message.UNINTERESTED;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendHave(int piece)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendBitfield(BitField bitfield)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendRequests(List requests)
|
||||
{
|
||||
Iterator it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
sendRequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
void sendRequest(Request req)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.REQUEST;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.PIECE;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.data = bytes;
|
||||
m.off = begin;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendCancel(Request req)
|
||||
{
|
||||
// See if it is still in our send queue
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST
|
||||
&& m.piece == req.piece
|
||||
&& m.begin == req.off
|
||||
&& m.length == req.len)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Always send, just to be sure it it is really canceled.
|
||||
Message m = new Message();
|
||||
m.type = Message.CANCEL;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
// Called by the PeerState when the other side doesn't want this
|
||||
// request to be handled anymore. Removes any pending Piece Message
|
||||
// from out send queue.
|
||||
void cancelRequest(int piece, int begin, int length)
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.PIECE
|
||||
&& m.piece == piece
|
||||
&& m.begin == begin
|
||||
&& m.length == length)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
577
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
577
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
@@ -0,0 +1,577 @@
|
||||
/* PeerCoordinator - Coordinates which peers do what (up and downloading).
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Coordinates what peer does what.
|
||||
*/
|
||||
public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = new Log(PeerCoordinator.class);
|
||||
final MetaInfo metainfo;
|
||||
final Storage storage;
|
||||
final Snark snark;
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||
final static int MAX_CONNECTIONS = 24;
|
||||
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
|
||||
|
||||
// Approximation of the number of current uploaders.
|
||||
// Resynced by PeerChecker once in a while.
|
||||
int uploaders = 0;
|
||||
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
/** estimate of the peers, without requiring any synchronization */
|
||||
volatile int peerCount;
|
||||
|
||||
/** Timer to handle all periodical tasks. */
|
||||
private final Timer timer = new Timer(true);
|
||||
|
||||
private final byte[] id;
|
||||
|
||||
// Some random wanted pieces
|
||||
private final List wantedPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
|
||||
private final CoordinatorListener listener;
|
||||
|
||||
public String trackerProblems = null;
|
||||
public int trackerSeenPeers = 0;
|
||||
|
||||
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
|
||||
CoordinatorListener listener, Snark torrent)
|
||||
{
|
||||
this.id = id;
|
||||
this.metainfo = metainfo;
|
||||
this.storage = storage;
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
for(int i = 0; i < metainfo.getPieces(); i++)
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
public CoordinatorListener getListener() { return listener; }
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean completed()
|
||||
{
|
||||
return storage.complete();
|
||||
}
|
||||
|
||||
public int getPeerCount() { return peerCount; }
|
||||
|
||||
public int getPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
int rv = peers.size();
|
||||
peerCount = rv;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many bytes are still needed to get the complete file.
|
||||
*/
|
||||
public long getLeft()
|
||||
{
|
||||
// XXX - Only an approximation.
|
||||
return storage.needed() * metainfo.getPieceLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of uploaded bytes of all peers.
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of downloaded bytes of all peers.
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
}
|
||||
|
||||
public boolean needPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return !halted && peers.size() < MAX_CONNECTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean halted() { return halted; }
|
||||
|
||||
public void halt()
|
||||
{
|
||||
halted = true;
|
||||
List removed = new ArrayList();
|
||||
synchronized(peers)
|
||||
{
|
||||
// Stop peer checker task.
|
||||
timer.cancel();
|
||||
|
||||
// Stop peers.
|
||||
removed.addAll(peers);
|
||||
peers.clear();
|
||||
peerCount = 0;
|
||||
}
|
||||
|
||||
while (removed.size() > 0) {
|
||||
Peer peer = (Peer)removed.remove(0);
|
||||
peer.disconnect();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public void connected(Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Peer toDisconnect = null;
|
||||
synchronized(peers)
|
||||
{
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
|
||||
// idle for 2 minutes, kill the old con
|
||||
peers.remove(old);
|
||||
toDisconnect = old;
|
||||
old = null;
|
||||
}
|
||||
if (old != null)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
|
||||
|
||||
// Add it to the beginning of the list.
|
||||
// And try to optimistically make it a uploader.
|
||||
peers.add(0, peer);
|
||||
peerCount = peers.size();
|
||||
unchokePeer();
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
}
|
||||
if (toDisconnect != null) {
|
||||
toDisconnect.disconnect(false);
|
||||
removePeerFromPieces(toDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
private static Peer peerIDInList(PeerID pid, List peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
if (pid.sameID(cur.getPeerID()))
|
||||
return cur;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addPeer(final Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean need_more;
|
||||
synchronized(peers)
|
||||
{
|
||||
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
|
||||
}
|
||||
|
||||
if (need_more)
|
||||
{
|
||||
_log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
|
||||
|
||||
// Run the peer with us as listener and the current bitfield.
|
||||
final PeerListener listener = this;
|
||||
final BitField bitfield = storage.getBitField();
|
||||
Runnable r = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
peer.runConnection(listener, bitfield);
|
||||
}
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new I2PThread(r, threadName).start();
|
||||
}
|
||||
else
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (peer.isConnected())
|
||||
_log.info("Add peer already connected: " + peer);
|
||||
else
|
||||
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// (Optimistically) unchoke. Should be called with peers synchronized
|
||||
void unchokePeer()
|
||||
{
|
||||
// linked list will contain all interested peers that we choke.
|
||||
// At the start are the peers that have us unchoked at the end the
|
||||
// other peer that are interested, but are choking us.
|
||||
List interested = new LinkedList();
|
||||
synchronized (peers) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unchoke: " + peer);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getBitMap()
|
||||
{
|
||||
return storage.getBitField().getFieldBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we don't have the given piece yet.
|
||||
*/
|
||||
public boolean gotHave(Peer peer, int piece)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
return wantedPieces.contains(new Piece(piece));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given bitfield contains at least one piece we
|
||||
* are interested in.
|
||||
*/
|
||||
public boolean gotBitField(Peer peer, BitField bitfield)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i))
|
||||
p.addPeer(peer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
*/
|
||||
public int wantPiece(Peer peer, BitField havePieces)
|
||||
{
|
||||
if (halted)
|
||||
return -1;
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Piece piece = null;
|
||||
Collections.sort(wantedPieces); // Sort in order of rarest first.
|
||||
List requested = new ArrayList();
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (piece == null && it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
if (havePieces.get(p.getId()) && !p.isRequested())
|
||||
{
|
||||
piece = p;
|
||||
}
|
||||
else if (p.isRequested())
|
||||
{
|
||||
requested.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
Iterator it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it2.next();
|
||||
if (havePieces.get(p.getId()))
|
||||
{
|
||||
piece = p;
|
||||
}
|
||||
}
|
||||
if (piece == null) return -1; //If we still can't find a piece we want, so be it.
|
||||
}
|
||||
piece.setRequested(true);
|
||||
return piece.getId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*/
|
||||
public byte[] gotRequest(Peer peer, int piece)
|
||||
{
|
||||
if (halted)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return storage.getPiece(piece);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
snark.stopTorrent();
|
||||
_log.error("Error reading the storage for " + metainfo.getName(), ioe);
|
||||
throw new RuntimeException("B0rked");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a peer has uploaded some bytes of a piece.
|
||||
*/
|
||||
public void uploaded(Peer peer, int size)
|
||||
{
|
||||
uploaded += size;
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a peer has downloaded some bytes of a piece.
|
||||
*/
|
||||
public void downloaded(Peer peer, int size)
|
||||
{
|
||||
downloaded += size;
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the piece is no good (according to the hash).
|
||||
* In that case the peer that supplied the piece should probably be
|
||||
* blacklisted.
|
||||
*/
|
||||
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||
{
|
||||
if (halted) {
|
||||
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
return true; // We don't actually care anymore.
|
||||
}
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Piece p = new Piece(piece);
|
||||
if (!wantedPieces.contains(p))
|
||||
{
|
||||
_log.info("Got unwanted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
|
||||
// No need to announce have piece to peers.
|
||||
// Assume we got a good piece, we don't really care anymore.
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (storage.putPiece(piece, bs))
|
||||
{
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
snark.stopTorrent();
|
||||
_log.error("Error writing storage for " + metainfo.getName(), ioe);
|
||||
throw new RuntimeException("B0rked");
|
||||
}
|
||||
wantedPieces.remove(p);
|
||||
}
|
||||
|
||||
// Announce to the world we have it!
|
||||
synchronized(peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.isConnected())
|
||||
p.have(piece);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void gotChoke(Peer peer, boolean choke)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got choke(" + choke + "): " + peer);
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
public void gotInterest(Peer peer, boolean interest)
|
||||
{
|
||||
if (interest)
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
if (uploaders < MAX_UPLOADERS)
|
||||
{
|
||||
if(peer.isChoking())
|
||||
{
|
||||
uploaders++;
|
||||
peer.setChoking(false);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unchoke: " + peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
public void disconnected(Peer peer)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Disconnected " + peer, new Exception("Disconnected by"));
|
||||
|
||||
synchronized(peers)
|
||||
{
|
||||
// Make sure it is no longer in our lists
|
||||
if (peers.remove(peer))
|
||||
{
|
||||
// Unchoke some random other peer
|
||||
unchokePeer();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
peerCount = peers.size();
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/** Called when a peer is removed, to prevent it from being used in
|
||||
* rarest-first calculations.
|
||||
*/
|
||||
public void removePeerFromPieces(Peer peer) {
|
||||
synchronized(wantedPieces) {
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
piece.removePeer(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Hmm, any guesses as to what this is? Used by the multitorrent functionality
|
||||
* in the PeerAcceptor to pick the right PeerCoordinator to accept the con for.
|
||||
* Each PeerCoordinator is added to the set from within the Snark (and removed
|
||||
* 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() {
|
||||
_coordinators = new HashSet();
|
||||
}
|
||||
|
||||
public Iterator iterator() {
|
||||
synchronized (_coordinators) {
|
||||
return new ArrayList(_coordinators).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public void add(PeerCoordinator coordinator) {
|
||||
synchronized (_coordinators) {
|
||||
_coordinators.add(coordinator);
|
||||
}
|
||||
}
|
||||
public void remove(PeerCoordinator coordinator) {
|
||||
synchronized (_coordinators) {
|
||||
_coordinators.remove(coordinator);
|
||||
}
|
||||
}
|
||||
}
|
||||
208
apps/i2psnark/java/src/org/klomp/snark/PeerID.java
Normal file
208
apps/i2psnark/java/src/org/klomp/snark/PeerID.java
Normal file
@@ -0,0 +1,208 @@
|
||||
/* PeerID - All public information concerning a peer.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
public class PeerID implements Comparable
|
||||
{
|
||||
private final byte[] id;
|
||||
private final Destination address;
|
||||
private final int port;
|
||||
|
||||
private final int hash;
|
||||
|
||||
public PeerID(byte[] id, Destination address)
|
||||
{
|
||||
this.id = id;
|
||||
this.address = address;
|
||||
this.port = 6881;
|
||||
|
||||
hash = calculateHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PeerID from a BDecoder.
|
||||
*/
|
||||
public PeerID(BDecoder be)
|
||||
throws IOException
|
||||
{
|
||||
this(be.bdecodeMap().getMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PeerID from a Map containing BEncoded peer id, ip and
|
||||
* port.
|
||||
*/
|
||||
public PeerID(Map m)
|
||||
throws InvalidBEncodingException, UnknownHostException
|
||||
{
|
||||
BEValue bevalue = (BEValue)m.get("peer id");
|
||||
if (bevalue == null)
|
||||
throw new InvalidBEncodingException("peer id missing");
|
||||
id = bevalue.getBytes();
|
||||
|
||||
bevalue = (BEValue)m.get("ip");
|
||||
if (bevalue == null)
|
||||
throw new InvalidBEncodingException("ip missing");
|
||||
address = I2PSnarkUtil.instance().getDestination(bevalue.getString());
|
||||
if (address == null)
|
||||
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
|
||||
|
||||
port = 6881;
|
||||
|
||||
hash = calculateHash();
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Destination getAddress()
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
private int calculateHash()
|
||||
{
|
||||
int b = 0;
|
||||
for (int i = 0; i < id.length; i++)
|
||||
b ^= id[i];
|
||||
return (b ^ address.hashCode()) ^ port;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code of a PeerID is the exclusive or of all id bytes.
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this peerID and the given peerID have
|
||||
* the same 20 bytes as ID.
|
||||
*/
|
||||
public boolean sameID(PeerID pid)
|
||||
{
|
||||
boolean equal = true;
|
||||
for (int i = 0; equal && i < id.length; i++)
|
||||
equal = id[i] == pid.id[i];
|
||||
return equal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Two PeerIDs are equal when they have the same id, address and port.
|
||||
*/
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o instanceof PeerID)
|
||||
{
|
||||
PeerID pid = (PeerID)o;
|
||||
|
||||
return port == pid.port
|
||||
&& address.equals(pid.address)
|
||||
&& sameID(pid);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares port, address and id.
|
||||
*/
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
PeerID pid = (PeerID)o;
|
||||
|
||||
int result = port - pid.port;
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = address.hashCode() - pid.address.hashCode();
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < id.length; i++)
|
||||
{
|
||||
result = id[i] - pid.id[i];
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String "id@address" where id is the base64 encoded id.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
int nonZero = 0;
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (id[i] != 0) {
|
||||
nonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Base64.encode(id, nonZero, id.length-nonZero).substring(0,4) + "@" + address.calculateHash().toBase64().substring(0,6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an id as a hex encoded string and remove leading zeros.
|
||||
*/
|
||||
public static String idencode(byte[] bs)
|
||||
{
|
||||
boolean leading_zeros = true;
|
||||
|
||||
StringBuffer sb = new StringBuffer(bs.length*2);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
{
|
||||
int c = bs[i] & 0xFF;
|
||||
if (leading_zeros && c == 0)
|
||||
continue;
|
||||
else
|
||||
leading_zeros = false;
|
||||
|
||||
if (c < 16)
|
||||
sb.append('0');
|
||||
sb.append(Integer.toHexString(c));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
145
apps/i2psnark/java/src/org/klomp/snark/PeerListener.java
Normal file
145
apps/i2psnark/java/src/org/klomp/snark/PeerListener.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/* PeerListener - Interface for listening to peer events.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Listener for Peer events.
|
||||
*/
|
||||
public interface PeerListener
|
||||
{
|
||||
/**
|
||||
* Called when the connection to the peer has started and the
|
||||
* handshake was successfull.
|
||||
*
|
||||
* @param peer the Peer that just got connected.
|
||||
*/
|
||||
void connected(Peer peer);
|
||||
|
||||
/**
|
||||
* Called when the connection to the peer was terminated or the
|
||||
* connection handshake failed.
|
||||
*
|
||||
* @param peer the Peer that just got disconnected.
|
||||
*/
|
||||
void disconnected(Peer peer);
|
||||
|
||||
/**
|
||||
* Called when a choke message is received.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param choke true when the peer got a choke message, false when
|
||||
* the peer got an unchoke message.
|
||||
*/
|
||||
void gotChoke(Peer peer, boolean choke);
|
||||
|
||||
/**
|
||||
* Called when an interested message is received.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param interest true when the peer got a interested message, false when
|
||||
* the peer got an uninterested message.
|
||||
*/
|
||||
void gotInterest(Peer peer, boolean interest);
|
||||
|
||||
/**
|
||||
* Called when a have piece message is received. If the method
|
||||
* returns true and the peer has not yet received a interested
|
||||
* message or we indicated earlier to be not interested then an
|
||||
* interested message will be send.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param piece the piece number that the per just got.
|
||||
*
|
||||
* @return true when it is a piece that we want, false if the piece is
|
||||
* already known.
|
||||
*/
|
||||
boolean gotHave(Peer peer, int piece);
|
||||
|
||||
/**
|
||||
* Called when a bitmap message is received. If this method returns
|
||||
* true a interested message will be send back to the peer.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param bitfield a BitField containing the pieces that the other
|
||||
* side has.
|
||||
*
|
||||
* @return true when the BitField contains pieces we want, false if
|
||||
* the piece is already known.
|
||||
*/
|
||||
boolean gotBitField(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when a piece is received from the peer. The piece must be
|
||||
* requested by Peer.request() first. If this method returns false
|
||||
* that means the Peer provided a corrupted piece and the connection
|
||||
* will be closed.
|
||||
*
|
||||
* @param peer the Peer that got the piece.
|
||||
* @param piece the piece number received.
|
||||
* @param bs the byte array containing the piece.
|
||||
*
|
||||
* @return true when the bytes represent the piece, false otherwise.
|
||||
*/
|
||||
boolean gotPiece(Peer peer, int piece, byte[] bs);
|
||||
|
||||
/**
|
||||
* Called when the peer wants (part of) a piece from us. Only called
|
||||
* when the peer is not choked by us (<code>peer.choke(false)</code>
|
||||
* was called).
|
||||
*
|
||||
* @param peer the Peer that wants the piece.
|
||||
* @param piece the piece number requested.
|
||||
*
|
||||
* @return a byte array containing the piece or null when the piece
|
||||
* is not available (which is a protocol error).
|
||||
*/
|
||||
byte[] gotRequest(Peer peer, int piece);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been downloaded from the peer.
|
||||
*
|
||||
* @param peer the Peer from which size bytes where downloaded.
|
||||
* @param size the number of bytes that where downloaded.
|
||||
*/
|
||||
void downloaded(Peer peer, int size);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been uploaded to the peer.
|
||||
*
|
||||
* @param peer the Peer to which size bytes where uploaded.
|
||||
* @param size the number of bytes that where uploaded.
|
||||
*/
|
||||
void uploaded(Peer peer, int size);
|
||||
|
||||
/**
|
||||
* Called when we are downloading from the peer and need to ask for
|
||||
* a new piece. Might be called multiple times before
|
||||
* <code>gotPiece()</code> is called.
|
||||
*
|
||||
* @param peer the Peer that will be asked to provide the piece.
|
||||
* @param bitfield a BitField containing the pieces that the other
|
||||
* side has.
|
||||
*
|
||||
* @return one of the pieces from the bitfield that we want or -1 if
|
||||
* we are no longer interested in the peer.
|
||||
*/
|
||||
int wantPiece(Peer peer, BitField bitfield);
|
||||
}
|
||||
128
apps/i2psnark/java/src/org/klomp/snark/PeerMonitorTask.java
Normal file
128
apps/i2psnark/java/src/org/klomp/snark/PeerMonitorTask.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/* PeerMonitorTasks - TimerTask that monitors the peers and total up/down speed
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TimerTask that monitors the peers and total up/download speeds.
|
||||
* Works together with the main Snark class to report periodical statistics.
|
||||
*/
|
||||
class PeerMonitorTask extends TimerTask
|
||||
{
|
||||
final static long MONITOR_PERIOD = 10 * 1000; // Ten seconds.
|
||||
private final long KILOPERSECOND = 1024 * (MONITOR_PERIOD / 1000);
|
||||
|
||||
private final PeerCoordinator coordinator;
|
||||
|
||||
private long lastDownloaded = 0;
|
||||
private long lastUploaded = 0;
|
||||
|
||||
PeerMonitorTask(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// Get some statistics
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
|
||||
// Don't list dying peers
|
||||
if (!peer.isConnected())
|
||||
continue;
|
||||
|
||||
peers++;
|
||||
|
||||
if (!peer.isChoking())
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
}
|
||||
}
|
||||
|
||||
// Print some statistics
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
String totalDown;
|
||||
if (downloaded >= 10 * 1024 * 1024)
|
||||
totalDown = (downloaded / (1024 * 1024)) + "MB";
|
||||
else
|
||||
totalDown = (downloaded / 1024 )+ "KB";
|
||||
long uploaded = coordinator.getUploaded();
|
||||
String totalUp;
|
||||
if (uploaded >= 10 * 1024 * 1024)
|
||||
totalUp = (uploaded / (1024 * 1024)) + "MB";
|
||||
else
|
||||
totalUp = (uploaded / 1024) + "KB";
|
||||
|
||||
int needP = coordinator.storage.needed();
|
||||
long needMB
|
||||
= needP * coordinator.metainfo.getPieceLength(0) / (1024 * 1024);
|
||||
int totalP = coordinator.metainfo.getPieces();
|
||||
long totalMB = coordinator.metainfo.getTotalLength() / (1024 * 1024);
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Down: "
|
||||
+ (downloaded - lastDownloaded) / KILOPERSECOND
|
||||
+ "KB/s"
|
||||
+ " (" + totalDown + ")"
|
||||
+ " Up: "
|
||||
+ (uploaded - lastUploaded) / KILOPERSECOND
|
||||
+ "KB/s"
|
||||
+ " (" + totalUp + ")"
|
||||
+ " Need " + needP
|
||||
+ " (" + needMB + "MB)"
|
||||
+ " of " + totalP
|
||||
+ " (" + totalMB + "MB)"
|
||||
+ " pieces");
|
||||
System.out.println(peers + ": Download #" + downloaders
|
||||
+ " Upload #" + uploaders
|
||||
+ " Interested #" + interested
|
||||
+ " Interesting #" + interesting
|
||||
+ " Choking #" + choking
|
||||
+ " Choked #" + choked);
|
||||
System.out.println();
|
||||
|
||||
lastDownloaded = downloaded;
|
||||
lastUploaded = uploaded;
|
||||
}
|
||||
}
|
||||
524
apps/i2psnark/java/src/org/klomp/snark/PeerState.java
Normal file
524
apps/i2psnark/java/src/org/klomp/snark/PeerState.java
Normal file
@@ -0,0 +1,524 @@
|
||||
/* PeerState - Keeps track of the Peer state through connection callbacks.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerState
|
||||
{
|
||||
private Log _log = new Log(PeerState.class);
|
||||
final Peer peer;
|
||||
final PeerListener listener;
|
||||
final MetaInfo metainfo;
|
||||
|
||||
// Interesting and choking describes whether we are interested in or
|
||||
// are choking the other side.
|
||||
boolean interesting = false;
|
||||
boolean choking = true;
|
||||
|
||||
// Interested and choked describes whether the other side is
|
||||
// interested in us or choked us.
|
||||
boolean interested = false;
|
||||
boolean choked = true;
|
||||
|
||||
// Package local for use by Peer.
|
||||
long downloaded;
|
||||
long uploaded;
|
||||
|
||||
BitField bitfield;
|
||||
|
||||
// Package local for use by Peer.
|
||||
final PeerConnectionIn in;
|
||||
final PeerConnectionOut out;
|
||||
|
||||
// Outstanding request
|
||||
private final List outstandingRequests = new ArrayList();
|
||||
private Request lastRequest = null;
|
||||
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 1;
|
||||
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
PeerConnectionIn in, PeerConnectionOut out)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.listener = listener;
|
||||
this.metainfo = metainfo;
|
||||
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
// NOTE Methods that inspect or change the state synchronize (on this).
|
||||
|
||||
void keepAliveMessage()
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv alive");
|
||||
/* XXX - ignored */
|
||||
}
|
||||
|
||||
void chokeMessage(boolean choke)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
|
||||
|
||||
choked = choke;
|
||||
if (choked)
|
||||
resend = true;
|
||||
|
||||
listener.gotChoke(peer, choke);
|
||||
|
||||
if (!choked && interesting)
|
||||
request();
|
||||
}
|
||||
|
||||
void interestedMessage(boolean interest)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv " + (interest ? "" : "un")
|
||||
+ "interested");
|
||||
interested = interest;
|
||||
listener.gotInterest(peer, interest);
|
||||
}
|
||||
|
||||
void haveMessage(int piece)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv have(" + piece + ")");
|
||||
// Sanity check
|
||||
if (piece < 0 || piece >= metainfo.getPieces())
|
||||
{
|
||||
// XXX disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Can happen if the other side never send a bitfield message.
|
||||
if (bitfield == null)
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
|
||||
bitfield.set(piece);
|
||||
}
|
||||
|
||||
if (listener.gotHave(peer, piece))
|
||||
setInteresting(true);
|
||||
}
|
||||
|
||||
void bitfieldMessage(byte[] bitmap)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv bitfield");
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you except?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got unexpected bitfield message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
}
|
||||
setInteresting(listener.gotBitField(peer, bitfield));
|
||||
}
|
||||
|
||||
void requestMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv request("
|
||||
+ piece + ", " + begin + ", " + length + ") ");
|
||||
if (choking)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Request received, but choking " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (piece < 0
|
||||
|| piece >= metainfo.getPieces()
|
||||
|| begin < 0
|
||||
|| begin > metainfo.getPieceLength(piece)
|
||||
|| length <= 0
|
||||
|| length > 4*PARTSIZE)
|
||||
{
|
||||
// XXX - Protocol error -> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got strange 'request: " + piece
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
return;
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||
{
|
||||
// XXX - Protocol error-> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got out of range 'request: " + piece
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer);
|
||||
out.sendPiece(piece, begin, length, pieceBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when some bytes have left the outgoing connection.
|
||||
* XXX - Should indicate whether it was a real piece or overhead.
|
||||
*/
|
||||
void uploaded(int size)
|
||||
{
|
||||
uploaded += size;
|
||||
listener.uploaded(peer, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a partial piece request has been handled by
|
||||
* PeerConnectionIn.
|
||||
*/
|
||||
void pieceMessage(Request req)
|
||||
{
|
||||
int size = req.len;
|
||||
downloaded += size;
|
||||
listener.downloaded(peer, size);
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
{
|
||||
if (listener.gotPiece(peer, req.piece, req.bs))
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got " + req.piece + ": " + peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.piece + " from " + peer);
|
||||
// XXX ARGH What now !?!
|
||||
downloaded = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private int getFirstOutstandingRequest(int piece)
|
||||
{
|
||||
for (int i = 0; i < outstandingRequests.size(); i++)
|
||||
if (((Request)outstandingRequests.get(i)).piece == piece)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a piece message is being processed by the incoming
|
||||
* connection. Returns null when there was no such request. It also
|
||||
* requeues/sends requests when it thinks that they must have been
|
||||
* lost.
|
||||
*/
|
||||
Request getOutstandingRequest(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getChunk("
|
||||
+ piece + "," + begin + "," + length + ") "
|
||||
+ peer);
|
||||
|
||||
int r = getFirstOutstandingRequest(piece);
|
||||
|
||||
// Unrequested piece number?
|
||||
if (r == -1)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unrequested 'piece: " + piece + ", "
|
||||
+ begin + ", " + length + "' received from "
|
||||
+ peer);
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
|
||||
// Lookup the correct piece chunk request from the list.
|
||||
Request req;
|
||||
synchronized(this)
|
||||
{
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
while (req.piece == piece && req.off != begin
|
||||
&& r < outstandingRequests.size() - 1)
|
||||
{
|
||||
r++;
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
}
|
||||
|
||||
// Something wrong?
|
||||
if (req.piece != piece || req.off != begin || req.len != length)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unrequested or unneeded 'piece: "
|
||||
+ piece + ", "
|
||||
+ begin + ", "
|
||||
+ length + "' received from "
|
||||
+ peer);
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
|
||||
// Report missing requests.
|
||||
if (r != 0)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Some requests dropped, got " + req
|
||||
+ ", wanted for peer: " + peer);
|
||||
for (int i = 0; i < r; i++)
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
// We used to rerequest the missing chunks but that mostly
|
||||
// just confuses the other side. So now we just keep
|
||||
// waiting for them. They will be rerequested when we get
|
||||
// choked/unchoked again.
|
||||
/*
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
*/
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("dropped " + dropReq + " with peer " + peer);
|
||||
}
|
||||
}
|
||||
outstandingRequests.remove(0);
|
||||
}
|
||||
|
||||
// Request more if necessary to keep the pipeline filled.
|
||||
addRequest();
|
||||
|
||||
return req;
|
||||
|
||||
}
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got cancel message ("
|
||||
+ piece + ", " + begin + ", " + length + ")");
|
||||
out.cancelRequest(piece, begin, length);
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Warning: Ignoring unknown message type: " + type
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
void havePiece(int piece)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Tell the other side that we are no longer interested in any of
|
||||
// the outstanding requests for this piece.
|
||||
if (lastRequest != null && lastRequest.piece == piece)
|
||||
lastRequest = null;
|
||||
|
||||
Iterator it = outstandingRequests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
if (req.piece == piece)
|
||||
{
|
||||
it.remove();
|
||||
// Send cancel even when we are choked to make sure that it is
|
||||
// really never ever send.
|
||||
out.sendCancel(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the other side that we really have this piece.
|
||||
out.sendHave(piece);
|
||||
|
||||
// Request something else if necessary.
|
||||
addRequest();
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Is the peer still interesting?
|
||||
if (lastRequest == null)
|
||||
setInteresting(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Starts or resumes requesting pieces.
|
||||
private void request()
|
||||
{
|
||||
// Are there outstanding requests that have to be resend?
|
||||
if (resend)
|
||||
{
|
||||
out.sendRequests(outstandingRequests);
|
||||
resend = false;
|
||||
}
|
||||
|
||||
// Add/Send some more requests if necessary.
|
||||
addRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
*/
|
||||
private void addRequest()
|
||||
{
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
}
|
||||
|
||||
// We want something and we don't have outstanding requests?
|
||||
if (more_pieces && lastRequest == null)
|
||||
more_pieces = requestNextPiece();
|
||||
else if (more_pieces) // We want something
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
synchronized(this)
|
||||
{
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
}
|
||||
|
||||
// Last part of a piece?
|
||||
if (isLastChunk)
|
||||
more_pieces = requestNextPiece();
|
||||
else
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
int nextPiece = lastRequest.piece;
|
||||
int nextBegin = lastRequest.off + PARTSIZE;
|
||||
byte[] bs = lastRequest.bs;
|
||||
int maxLength = pieceLength - nextBegin;
|
||||
int nextLength = maxLength > PARTSIZE ? PARTSIZE
|
||||
: maxLength;
|
||||
Request req
|
||||
= new Request(nextPiece, bs, nextBegin, nextLength);
|
||||
outstandingRequests.add(req);
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " requests " + outstandingRequests);
|
||||
}
|
||||
|
||||
// Starts requesting first chunk of next piece. Returns true if
|
||||
// something has been added to the requests, false otherwise.
|
||||
private boolean requestNextPiece()
|
||||
{
|
||||
// Check that we already know what the other side has.
|
||||
if (bitfield != null)
|
||||
{
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
synchronized(this)
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||
byte[] bs = new byte[piece_length];
|
||||
|
||||
int length = Math.min(piece_length, PARTSIZE);
|
||||
Request req = new Request(nextPiece, bs, 0, length);
|
||||
outstandingRequests.add(req);
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized void setInteresting(boolean interest)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setInteresting(" + interest + ")");
|
||||
|
||||
if (interest != interesting)
|
||||
{
|
||||
interesting = interest;
|
||||
out.sendInterest(interest);
|
||||
|
||||
if (interesting && !choked)
|
||||
request();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void setChoking(boolean choke)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setChoking(" + choke + ")");
|
||||
|
||||
if (choking != choke)
|
||||
{
|
||||
choking = choke;
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
apps/i2psnark/java/src/org/klomp/snark/Piece.java
Normal file
38
apps/i2psnark/java/src/org/klomp/snark/Piece.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Piece implements Comparable {
|
||||
|
||||
private int id;
|
||||
private Set peers;
|
||||
private boolean requested;
|
||||
|
||||
public Piece(int id) {
|
||||
this.id = id;
|
||||
this.peers = Collections.synchronizedSet(new HashSet());
|
||||
this.requested = false;
|
||||
}
|
||||
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
return this.peers.size() - ((Piece)o).peers.size();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
try {
|
||||
return this.id == ((Piece)o).id;
|
||||
} catch (ClassCastException cce) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getId() { return this.id; }
|
||||
public Set getPeers() { return this.peers; }
|
||||
public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
|
||||
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
|
||||
public boolean isRequested() { return this.requested; }
|
||||
public void setRequested(boolean requested) { this.requested = requested; }
|
||||
}
|
||||
73
apps/i2psnark/java/src/org/klomp/snark/Request.java
Normal file
73
apps/i2psnark/java/src/org/klomp/snark/Request.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/* Request - Holds all information needed for a (partial) piece request.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Holds all information needed for a partial piece request.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
final int piece;
|
||||
final byte[] bs;
|
||||
final int off;
|
||||
final int len;
|
||||
|
||||
/**
|
||||
* Creates a new Request.
|
||||
*
|
||||
* @param piece Piece number requested.
|
||||
* @param bs byte array where response should be stored.
|
||||
* @param off the offset in the array.
|
||||
* @param len the number of bytes requested.
|
||||
*/
|
||||
Request(int piece, byte[] bs, int off, int len)
|
||||
{
|
||||
this.piece = piece;
|
||||
this.bs = bs;
|
||||
this.off = off;
|
||||
this.len = len;
|
||||
|
||||
// Sanity check
|
||||
if (piece < 0 || off < 0 || len <= 0 || off + len > bs.length)
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return piece ^ off ^ len;
|
||||
}
|
||||
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o instanceof Request)
|
||||
{
|
||||
Request req = (Request)o;
|
||||
return req.piece == piece && req.off == off && req.len == len;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "(" + piece + "," + off + "," + len + ")";
|
||||
}
|
||||
}
|
||||
34
apps/i2psnark/java/src/org/klomp/snark/ShutdownListener.java
Normal file
34
apps/i2psnark/java/src/org/klomp/snark/ShutdownListener.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/* ShutdownListener - Callback for end of shutdown sequence
|
||||
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback for end of shutdown sequence.
|
||||
*/
|
||||
interface ShutdownListener
|
||||
{
|
||||
/**
|
||||
* Called when the SnarkShutdown hook has finished shutting down all
|
||||
* subcomponents.
|
||||
*/
|
||||
void shutdown();
|
||||
}
|
||||
700
apps/i2psnark/java/src/org/klomp/snark/Snark.java
Normal file
700
apps/i2psnark/java/src/org/klomp/snark/Snark.java
Normal file
@@ -0,0 +1,700 @@
|
||||
/* Snark - Main snark program startup class.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Main Snark program startup class.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class Snark
|
||||
implements StorageListener, CoordinatorListener, ShutdownListener
|
||||
{
|
||||
private final static int MIN_PORT = 6881;
|
||||
private final static int MAX_PORT = 6889;
|
||||
|
||||
// Error messages (non-fatal)
|
||||
public final static int ERROR = 1;
|
||||
|
||||
// Warning messages
|
||||
public final static int WARNING = 2;
|
||||
|
||||
// Notices (peer level)
|
||||
public final static int NOTICE = 3;
|
||||
|
||||
// Info messages (protocol policy level)
|
||||
public final static int INFO = 4;
|
||||
|
||||
// Debug info (protocol level)
|
||||
public final static int DEBUG = 5;
|
||||
|
||||
// Very low level stuff (network level)
|
||||
public final static int ALL = 6;
|
||||
|
||||
/**
|
||||
* What level of debug info to show.
|
||||
*/
|
||||
public static int debug = NOTICE;
|
||||
|
||||
// Whether or not to ask the user for commands while sharing
|
||||
private static boolean command_interpreter = true;
|
||||
|
||||
private static final String newline = System.getProperty("line.separator");
|
||||
|
||||
private static final String copyright =
|
||||
"The Hunting of the Snark Project - Copyright (C) 2003 Mark J. Wielaard"
|
||||
+ newline + newline
|
||||
+ "Snark comes with ABSOLUTELY NO WARRANTY. This is free software, and"
|
||||
+ newline
|
||||
+ "you are welcome to redistribute it under certain conditions; read the"
|
||||
+ newline
|
||||
+ "COPYING file for details." + newline + newline
|
||||
+ "This is the I2P port, allowing anonymous bittorrent (http://www.i2p.net/)" + newline
|
||||
+ "It will not work with normal torrents, so don't even try ;)";
|
||||
|
||||
private static final String usage =
|
||||
"Press return for help. Type \"quit\" and return to stop.";
|
||||
private static final String help =
|
||||
"Commands: 'info', 'list', 'quit'.";
|
||||
|
||||
// String indicating main activity
|
||||
String activity = "Not started";
|
||||
|
||||
private static class OOMListener implements I2PThread.OOMEventListener {
|
||||
public void outOfMemory(OutOfMemoryError err) {
|
||||
try {
|
||||
err.printStackTrace();
|
||||
I2PSnarkUtil.instance().debug("OOM in the snark", Snark.ERROR, err);
|
||||
} catch (Throwable t) {
|
||||
System.out.println("OOM in the OOM");
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
System.out.println(copyright);
|
||||
System.out.println();
|
||||
|
||||
if ( (args.length > 0) && ("--config".equals(args[0])) ) {
|
||||
I2PThread.addOOMEventListener(new OOMListener());
|
||||
SnarkManager sm = SnarkManager.instance();
|
||||
if (args.length > 1)
|
||||
sm.loadConfig(args[1]);
|
||||
System.out.println("Running in multitorrent mode");
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (sm) {
|
||||
sm.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse debug, share/ip and torrent file options.
|
||||
Snark snark = parseArguments(args);
|
||||
|
||||
SnarkShutdown snarkhook
|
||||
= new SnarkShutdown(snark.storage,
|
||||
snark.coordinator,
|
||||
snark.acceptor,
|
||||
snark.trackerclient,
|
||||
snark);
|
||||
//Runtime.getRuntime().addShutdownHook(snarkhook);
|
||||
|
||||
Timer timer = new Timer(true);
|
||||
TimerTask monitor = new PeerMonitorTask(snark.coordinator);
|
||||
timer.schedule(monitor,
|
||||
PeerMonitorTask.MONITOR_PERIOD,
|
||||
PeerMonitorTask.MONITOR_PERIOD);
|
||||
|
||||
// Start command interpreter
|
||||
if (Snark.command_interpreter)
|
||||
{
|
||||
boolean quit = false;
|
||||
|
||||
System.out.println();
|
||||
System.out.println(usage);
|
||||
System.out.println();
|
||||
|
||||
try
|
||||
{
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader(System.in));
|
||||
String line = br.readLine();
|
||||
while(!quit && line != null)
|
||||
{
|
||||
line = line.toLowerCase();
|
||||
if ("quit".equals(line))
|
||||
quit = true;
|
||||
else if ("list".equals(line))
|
||||
{
|
||||
synchronized(snark.coordinator.peers)
|
||||
{
|
||||
System.out.println(snark.coordinator.peers.size()
|
||||
+ " peers -"
|
||||
+ " (i)nterested,"
|
||||
+ " (I)nteresting,"
|
||||
+ " (c)hoking,"
|
||||
+ " (C)hoked:");
|
||||
Iterator it = snark.coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
System.out.println(peer);
|
||||
System.out.println("\ti: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("info".equals(line))
|
||||
{
|
||||
System.out.println("Name: " + snark.meta.getName());
|
||||
System.out.println("Torrent: " + snark.torrent);
|
||||
System.out.println("Tracker: " + snark.meta.getAnnounce());
|
||||
List files = snark.meta.getFiles();
|
||||
System.out.println("Files: "
|
||||
+ ((files == null) ? 1 : files.size()));
|
||||
System.out.println("Pieces: " + snark.meta.getPieces());
|
||||
System.out.println("Piece size: "
|
||||
+ snark.meta.getPieceLength(0) / 1024
|
||||
+ " KB");
|
||||
System.out.println("Total size: "
|
||||
+ snark.meta.getTotalLength() / (1024 * 1024)
|
||||
+ " MB");
|
||||
}
|
||||
else if ("".equals(line) || "help".equals(line))
|
||||
{
|
||||
System.out.println(usage);
|
||||
System.out.println(help);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Unknown command: " + line);
|
||||
System.out.println(usage);
|
||||
}
|
||||
|
||||
if (!quit)
|
||||
{
|
||||
System.out.println();
|
||||
line = br.readLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
debug("ERROR while reading stdin: " + ioe, ERROR);
|
||||
}
|
||||
|
||||
// Explicit shutdown.
|
||||
Runtime.getRuntime().removeShutdownHook(snarkhook);
|
||||
snarkhook.start();
|
||||
}
|
||||
}
|
||||
|
||||
public String torrent;
|
||||
public MetaInfo meta;
|
||||
public Storage storage;
|
||||
public PeerCoordinator coordinator;
|
||||
public ConnectionAcceptor acceptor;
|
||||
public TrackerClient trackerclient;
|
||||
public String rootDataDir = ".";
|
||||
public CompleteListener completeListener;
|
||||
public boolean stopped;
|
||||
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
this(torrent, ip, user_port, slistener, clistener, true, ".");
|
||||
}
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener, boolean start, String rootDir)
|
||||
{
|
||||
if (slistener == null)
|
||||
slistener = this;
|
||||
|
||||
if (clistener == null)
|
||||
clistener = this;
|
||||
|
||||
this.torrent = torrent;
|
||||
this.rootDataDir = rootDir;
|
||||
|
||||
stopped = true;
|
||||
activity = "Network setup";
|
||||
|
||||
// "Taking Three as the subject to reason about--
|
||||
// A convenient number to state--
|
||||
// We add Seven, and Ten, and then multiply out
|
||||
// By One Thousand diminished by Eight.
|
||||
//
|
||||
// "The result we proceed to divide, as you see,
|
||||
// By Nine Hundred and Ninety Two:
|
||||
// Then subtract Seventeen, and the answer must be
|
||||
// Exactly and perfectly true.
|
||||
|
||||
// Create a new ID and fill it with something random. First nine
|
||||
// zeros bytes, then three bytes filled with snark and then
|
||||
// sixteen random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] id = new byte[20];
|
||||
Random random = new Random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
id[i] = 0;
|
||||
id[i++] = snark;
|
||||
id[i++] = snark;
|
||||
id[i++] = snark;
|
||||
while (i < 20)
|
||||
id[i++] = (byte)random.nextInt(256);
|
||||
|
||||
Snark.debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||
|
||||
int port;
|
||||
IOException lastException = null;
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else
|
||||
debug("Listening on I2P destination " + serversocket.getManager().getSession().getMyDestination().toBase64(), NOTICE);
|
||||
|
||||
// Figure out what the torrent argument represents.
|
||||
meta = null;
|
||||
File f = null;
|
||||
try
|
||||
{
|
||||
InputStream in = null;
|
||||
f = new File(torrent);
|
||||
if (f.exists())
|
||||
in = new FileInputStream(f);
|
||||
else
|
||||
{
|
||||
activity = "Getting torrent";
|
||||
File torrentFile = I2PSnarkUtil.instance().get(torrent);
|
||||
if (torrentFile == null) {
|
||||
fatal("Unable to fetch " + torrent);
|
||||
if (false) return; // never reached - fatal(..) throws
|
||||
} else {
|
||||
torrentFile.deleteOnExit();
|
||||
in = new FileInputStream(torrentFile);
|
||||
}
|
||||
}
|
||||
meta = new MetaInfo(new BDecoder(in));
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
// OK, so it wasn't a torrent metainfo file.
|
||||
if (f != null && f.exists())
|
||||
if (ip == null)
|
||||
fatal("'" + torrent + "' exists,"
|
||||
+ " but is not a valid torrent metainfo file."
|
||||
+ System.getProperty("line.separator"), ioe);
|
||||
else
|
||||
fatal("I2PSnark does not support creating and tracking a torrent at the moment");
|
||||
/*
|
||||
{
|
||||
// Try to create a new metainfo file
|
||||
Snark.debug
|
||||
("Trying to create metainfo torrent for '" + torrent + "'",
|
||||
NOTICE);
|
||||
try
|
||||
{
|
||||
activity = "Creating torrent";
|
||||
storage = new Storage
|
||||
(f, "http://" + ip + ":" + port + "/announce", slistener);
|
||||
storage.create();
|
||||
meta = storage.getMetaInfo();
|
||||
}
|
||||
catch (IOException ioe2)
|
||||
{
|
||||
fatal("Could not create torrent for '" + torrent + "'", ioe2);
|
||||
}
|
||||
}
|
||||
*/
|
||||
else
|
||||
fatal("Cannot open '" + torrent + "'", ioe);
|
||||
}
|
||||
|
||||
debug(meta.toString(), INFO);
|
||||
|
||||
// When the metainfo torrent was created from an existing file/dir
|
||||
// it already exists.
|
||||
if (storage == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(meta, slistener);
|
||||
storage.check(rootDataDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not create storage", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
if (start)
|
||||
startTorrent();
|
||||
}
|
||||
/**
|
||||
* Start up contacting peers and querying the tracker
|
||||
*/
|
||||
public void startTorrent() {
|
||||
stopped = false;
|
||||
boolean coordinatorChanged = false;
|
||||
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(),
|
||||
coordinator.getStorage(), coordinator.getListener(), this);
|
||||
set.add(newCoord);
|
||||
coordinator = newCoord;
|
||||
coordinatorChanged = true;
|
||||
}
|
||||
if (!trackerclient.started() && !coordinatorChanged) {
|
||||
trackerclient.start();
|
||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
trackerclient = newClient;
|
||||
trackerclient.start();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stop contacting the tracker and talking with peers
|
||||
*/
|
||||
public void stopTorrent() {
|
||||
stopped = true;
|
||||
trackerclient.halt();
|
||||
coordinator.halt();
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
PeerCoordinatorSet.instance().remove(coordinator);
|
||||
}
|
||||
|
||||
static Snark parseArguments(String[] args)
|
||||
{
|
||||
return parseArguments(args, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets debug, ip and torrent variables then creates a Snark
|
||||
* instance. Calls usage(), which terminates the program, if
|
||||
* non-valid argument list. The given listeners will be
|
||||
* passed to all components that take one.
|
||||
*/
|
||||
static Snark parseArguments(String[] args,
|
||||
StorageListener slistener,
|
||||
CoordinatorListener clistener)
|
||||
{
|
||||
int user_port = -1;
|
||||
String ip = null;
|
||||
String torrent = null;
|
||||
|
||||
boolean configured = I2PSnarkUtil.instance().configured();
|
||||
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
debug = INFO;
|
||||
i++;
|
||||
|
||||
// Try if there is an level argument.
|
||||
if (i < args.length)
|
||||
{
|
||||
try
|
||||
{
|
||||
int level = Integer.parseInt(args[i]);
|
||||
if (level >= 0)
|
||||
{
|
||||
debug = level;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException nfe) { }
|
||||
}
|
||||
}
|
||||
else if (args[i].equals("--port"))
|
||||
{
|
||||
if (args.length - 1 < i + 1)
|
||||
usage("--port needs port number to listen on");
|
||||
try
|
||||
{
|
||||
user_port = Integer.parseInt(args[i + 1]);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
usage("--port argument must be a number (" + nfe + ")");
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
else if (args[i].equals("--no-commands"))
|
||||
{
|
||||
command_interpreter = false;
|
||||
i++;
|
||||
}
|
||||
else if (args[i].equals("--eepproxy"))
|
||||
{
|
||||
String proxyHost = args[i+1];
|
||||
String proxyPort = args[i+2];
|
||||
if (!configured)
|
||||
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
i += 3;
|
||||
}
|
||||
else if (args[i].equals("--i2cp"))
|
||||
{
|
||||
String i2cpHost = args[i+1];
|
||||
String i2cpPort = args[i+2];
|
||||
Properties opts = null;
|
||||
if (i+3 < args.length) {
|
||||
if (!args[i+3].startsWith("--")) {
|
||||
opts = new Properties();
|
||||
StringTokenizer tok = new StringTokenizer(args[i+3], " \t");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String str = tok.nextToken();
|
||||
int split = str.indexOf('=');
|
||||
if (split > 0) {
|
||||
opts.setProperty(str.substring(0, split), str.substring(split+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!configured)
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||
i += 3 + (opts != null ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
torrent = args[i];
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (torrent == null || i != args.length)
|
||||
if (torrent != null && torrent.startsWith("-"))
|
||||
usage("Unknow option '" + torrent + "'.");
|
||||
else
|
||||
usage("Need exactly one <url>, <file> or <dir>.");
|
||||
|
||||
return new Snark(torrent, ip, user_port, slistener, clistener);
|
||||
}
|
||||
|
||||
private static void usage(String s)
|
||||
{
|
||||
System.out.println("snark: " + s);
|
||||
usage();
|
||||
}
|
||||
|
||||
private static void usage()
|
||||
{
|
||||
System.out.println
|
||||
("Usage: snark [--debug [level]] [--no-commands] [--port <port>]");
|
||||
System.out.println
|
||||
(" [--eepproxy hostname portnum]");
|
||||
System.out.println
|
||||
(" [--i2cp routerHost routerPort ['name=val name=val name=val']]");
|
||||
System.out.println
|
||||
(" (<url>|<file>)");
|
||||
System.out.println
|
||||
(" --debug\tShows some extra info and stacktraces");
|
||||
System.out.println
|
||||
(" level\tHow much debug details to show");
|
||||
System.out.println
|
||||
(" \t(defaults to "
|
||||
+ NOTICE + ", with --debug to "
|
||||
+ INFO + ", highest level is "
|
||||
+ ALL + ").");
|
||||
System.out.println
|
||||
(" --no-commands\tDon't read interactive commands or show usage info.");
|
||||
System.out.println
|
||||
(" --port\tThe port to listen on for incomming connections");
|
||||
System.out.println
|
||||
(" \t(if not given defaults to first free port between "
|
||||
+ MIN_PORT + "-" + MAX_PORT + ").");
|
||||
System.out.println
|
||||
(" --share\tStart torrent tracker on <ip> address or <host> name.");
|
||||
System.out.println
|
||||
(" --eepproxy\thttp proxy to use (default of 127.0.0.1 port 4444)");
|
||||
System.out.println
|
||||
(" --i2cp\tlocation of your I2P router (default of 127.0.0.1 port 7654)");
|
||||
System.out.println
|
||||
(" \toptional settings may be included, such as");
|
||||
System.out.println
|
||||
(" \tinbound.length=2 outbound.length=2 inbound.lengthVariance=-1 ");
|
||||
System.out.println
|
||||
(" <url> \tURL pointing to .torrent metainfo file to download/share.");
|
||||
System.out.println
|
||||
(" <file> \tEither a local .torrent metainfo file to download");
|
||||
System.out.println
|
||||
(" \tor (with --share) a file to share.");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
*/
|
||||
public void fatal(String s)
|
||||
{
|
||||
fatal(s, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
*/
|
||||
public void fatal(String s, Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, ERROR, t);
|
||||
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
|
||||
//if (debug >= INFO && t != null)
|
||||
// t.printStackTrace();
|
||||
stopTorrent();
|
||||
throw new RuntimeException("die bart die");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show debug info if debug is true.
|
||||
*/
|
||||
public static void debug(String s, int level)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, level, null);
|
||||
//if (debug >= level)
|
||||
// System.out.println(s);
|
||||
}
|
||||
|
||||
public void peerChange(PeerCoordinator coordinator, Peer peer)
|
||||
{
|
||||
// System.out.println(peer.toString());
|
||||
}
|
||||
|
||||
boolean allocating = false;
|
||||
public void storageCreateFile(Storage storage, String name, long length)
|
||||
{
|
||||
if (allocating)
|
||||
System.out.println(); // Done with last file.
|
||||
|
||||
System.out.print("Creating file '" + name
|
||||
+ "' of length " + length + ": ");
|
||||
allocating = true;
|
||||
}
|
||||
|
||||
// How much storage space has been allocated
|
||||
private long allocated = 0;
|
||||
|
||||
public void storageAllocated(Storage storage, long length)
|
||||
{
|
||||
allocating = true;
|
||||
System.out.print(".");
|
||||
allocated += length;
|
||||
if (allocated == meta.getTotalLength())
|
||||
System.out.println(); // We have all the disk space we need.
|
||||
}
|
||||
|
||||
boolean allChecked = false;
|
||||
boolean checking = false;
|
||||
boolean prechecking = true;
|
||||
public void storageChecked(Storage storage, int num, boolean checked)
|
||||
{
|
||||
allocating = false;
|
||||
if (!allChecked && !checking)
|
||||
{
|
||||
// Use the MetaInfo from the storage since our own might not
|
||||
// yet be setup correctly.
|
||||
MetaInfo meta = storage.getMetaInfo();
|
||||
if (meta != null)
|
||||
System.out.print("Checking existing "
|
||||
+ meta.getPieces()
|
||||
+ " pieces: ");
|
||||
checking = true;
|
||||
}
|
||||
if (checking)
|
||||
if (checked)
|
||||
System.out.print("+");
|
||||
else
|
||||
System.out.print("-");
|
||||
else
|
||||
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
|
||||
Snark.INFO);
|
||||
}
|
||||
|
||||
public void storageAllChecked(Storage storage)
|
||||
{
|
||||
if (checking)
|
||||
System.out.println();
|
||||
|
||||
allChecked = true;
|
||||
checking = false;
|
||||
}
|
||||
|
||||
public void storageCompleted(Storage storage)
|
||||
{
|
||||
Snark.debug("Completely received " + torrent, Snark.INFO);
|
||||
//storage.close();
|
||||
System.out.println("Completely received: " + torrent);
|
||||
if (completeListener != null)
|
||||
completeListener.torrentComplete(this);
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
// have died. But in reality this does not always happen.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public interface CompleteListener {
|
||||
public void torrentComplete(Snark snark);
|
||||
}
|
||||
}
|
||||
470
apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
Normal file
470
apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
Normal file
@@ -0,0 +1,470 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Manage multiple snarks
|
||||
*/
|
||||
public class SnarkManager implements Snark.CompleteListener {
|
||||
private static SnarkManager _instance = new SnarkManager();
|
||||
public static SnarkManager instance() { return _instance; }
|
||||
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private Map _snarks;
|
||||
private String _configFile;
|
||||
private Properties _config;
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private List _messages;
|
||||
|
||||
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
|
||||
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
|
||||
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
|
||||
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
|
||||
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart";
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
|
||||
private SnarkManager() {
|
||||
_snarks = new HashMap();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
loadConfig("i2psnark.config");
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
}
|
||||
|
||||
private static final int MAX_MESSAGES = 5;
|
||||
public void addMessage(String message) {
|
||||
synchronized (_messages) {
|
||||
_messages.add(message);
|
||||
while (_messages.size() > MAX_MESSAGES)
|
||||
_messages.remove(0);
|
||||
}
|
||||
_log.info("MSG: " + message);
|
||||
}
|
||||
|
||||
/** newest last */
|
||||
public List getMessages() {
|
||||
synchronized (_messages) {
|
||||
return new ArrayList(_messages);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldAutoStart() {
|
||||
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
|
||||
}
|
||||
private int getStartupDelayMinutes() { return 1; }
|
||||
public File getDataDir() {
|
||||
String dir = _config.getProperty(PROP_DIR);
|
||||
if ( (dir == null) || (dir.trim().length() <= 0) )
|
||||
dir = "i2psnark";
|
||||
return new File(dir);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
// now add sane defaults
|
||||
if (!_config.containsKey(PROP_I2CP_HOST))
|
||||
_config.setProperty(PROP_I2CP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_I2CP_PORT))
|
||||
_config.setProperty(PROP_I2CP_PORT, "7654");
|
||||
if (!_config.containsKey(PROP_EEP_HOST))
|
||||
_config.setProperty(PROP_EEP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
_config.setProperty(PROP_EEP_PORT, "4444");
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, "i2psnark");
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
private void updateConfig() {
|
||||
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
|
||||
int i2cpPort = getInt(PROP_I2CP_PORT, 7654);
|
||||
String opts = _config.getProperty(PROP_I2CP_OPTS);
|
||||
Map i2cpOpts = new HashMap();
|
||||
if (opts != null) {
|
||||
StringTokenizer tok = new StringTokenizer(opts, " ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split > 0)
|
||||
i2cpOpts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
I2PSnarkUtil.instance().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);
|
||||
getDataDir().mkdirs();
|
||||
}
|
||||
|
||||
private int getInt(String prop, int defaultVal) {
|
||||
String p = _config.getProperty(prop);
|
||||
try {
|
||||
if ( (p != null) && (p.trim().length() > 0) )
|
||||
return Integer.parseInt(p.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
|
||||
boolean changed = false;
|
||||
if (eepHost != null) {
|
||||
int port = I2PSnarkUtil.instance().getEepProxyPort();
|
||||
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
|
||||
String host = I2PSnarkUtil.instance().getEepProxyHost();
|
||||
if ( (eepHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, port);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_EEP_HOST, eepHost);
|
||||
_config.setProperty(PROP_EEP_PORT, eepPort+"");
|
||||
addMessage("EepProxy location changed to " + eepHost + ":" + port);
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
|
||||
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
|
||||
int port = oldI2CPPort;
|
||||
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
|
||||
String host = oldI2CPHost;
|
||||
Map opts = new HashMap();
|
||||
if (i2cpOpts == null) i2cpOpts = "";
|
||||
StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split > 0)
|
||||
opts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
Map oldOpts = new HashMap();
|
||||
String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS);
|
||||
if (oldI2CPOpts == null) oldI2CPOpts = "";
|
||||
tok = new StringTokenizer(oldI2CPOpts, " \t\n");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split > 0)
|
||||
oldOpts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
|
||||
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(i2cpHost) ||
|
||||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
|
||||
(!oldOpts.equals(opts)))) ) {
|
||||
boolean snarksActive = false;
|
||||
Set names = listTorrentFiles();
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
Snark snark = getTorrent((String)iter.next());
|
||||
if ( (snark != null) && (!snark.stopped) ) {
|
||||
snarksActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (snarksActive) {
|
||||
addMessage("Cannot change the I2CP settings while torrents are active");
|
||||
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
|
||||
+ "] oldOpts [" + oldOpts + "]");
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().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();
|
||||
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();
|
||||
if (!ok)
|
||||
addMessage("Unable to reconnect with the old settings!");
|
||||
} else {
|
||||
addMessage("Reconnected on the new I2CP destination");
|
||||
_config.setProperty(PROP_I2CP_HOST, i2cpHost.trim());
|
||||
_config.setProperty(PROP_I2CP_PORT, "" + port);
|
||||
_config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim());
|
||||
changed = true;
|
||||
// no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = getTorrent(name);
|
||||
if ( (snark != null) && (snark.acceptor != null) ) {
|
||||
snark.acceptor.restart();
|
||||
addMessage("I2CP listener restarted for " + snark.meta.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (shouldAutoStart() != autoStart) {
|
||||
_config.setProperty(PROP_AUTO_START, autoStart + "");
|
||||
addMessage("Adjusted autostart to " + autoStart);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
saveConfig();
|
||||
} else {
|
||||
addMessage("Configuration unchanged");
|
||||
}
|
||||
}
|
||||
|
||||
public void saveConfig() {
|
||||
try {
|
||||
DataHelper.storeProps(_config, new File(_configFile));
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Unable to save the config to '" + _configFile + "'");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/** set of filenames that we are dealing with */
|
||||
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
|
||||
/**
|
||||
* Grab the torrent given the (canonical) filename
|
||||
*/
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) {
|
||||
if (!I2PSnarkUtil.instance().connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
addMessage("Error connecting to I2P - check your I2CP settings");
|
||||
return;
|
||||
}
|
||||
}
|
||||
File sfile = new File(filename);
|
||||
try {
|
||||
filename = sfile.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to add the torrent " + filename, ioe);
|
||||
addMessage("ERR: Could not add the torrent '" + filename + "': " + ioe.getMessage());
|
||||
return;
|
||||
}
|
||||
File dataDir = getDataDir();
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
if (torrent == null) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(sfile);
|
||||
MetaInfo info = new MetaInfo(fis);
|
||||
fis.close();
|
||||
fis = null;
|
||||
|
||||
String rejectMessage = locked_validateTorrent(info);
|
||||
if (rejectMessage != null) {
|
||||
sfile.delete();
|
||||
addMessage(rejectMessage);
|
||||
return;
|
||||
} else {
|
||||
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
|
||||
torrent.completeListener = this;
|
||||
_snarks.put(filename, torrent);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
|
||||
if (sfile.exists())
|
||||
sfile.delete();
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ok, snark created, now lets start it up or configure it further
|
||||
File f = new File(filename);
|
||||
if (shouldAutoStart()) {
|
||||
torrent.startTorrent();
|
||||
addMessage("Torrent added and started: '" + f.getName() + "'");
|
||||
} else {
|
||||
addMessage("Torrent added: '" + f.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private String locked_validateTorrent(MetaInfo info) throws IOException {
|
||||
List files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
|
||||
} else if (info.getPieces() <= 0) {
|
||||
return "No pieces in " + info.getName() + "? deleting it";
|
||||
} else if (info.getPieceLength(0) > 10*1024*1024) {
|
||||
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB, deleting it";
|
||||
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
List lengths = info.getLengths();
|
||||
if (lengths != null)
|
||||
for (int i = 0; i < lengths.size(); i++)
|
||||
System.out.println("File " + i + " is " + lengths.get(i) + " long");
|
||||
|
||||
return "Torrents larger than 10GB are not supported yet (because we're paranoid): " + info.getName() + ", deleting it";
|
||||
} else {
|
||||
// ok
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the torrent, leaving it on the list of torrents unless told to remove it
|
||||
*/
|
||||
public Snark stopTorrent(String filename, boolean shouldRemove) {
|
||||
File sfile = new File(filename);
|
||||
try {
|
||||
filename = sfile.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to remove the torrent " + filename, ioe);
|
||||
addMessage("ERR: Could not remove the torrent '" + filename + "': " + ioe.getMessage());
|
||||
return null;
|
||||
}
|
||||
int remaining = 0;
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
if (shouldRemove)
|
||||
torrent = (Snark)_snarks.remove(filename);
|
||||
else
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
remaining = _snarks.size();
|
||||
}
|
||||
if (torrent != null) {
|
||||
boolean wasStopped = torrent.stopped;
|
||||
torrent.stopTorrent();
|
||||
if (remaining == 0) {
|
||||
// should we disconnect/reconnect here (taking care to deal with the other thread's
|
||||
// I2PServerSocket.accept() call properly?)
|
||||
////I2PSnarkUtil.instance().
|
||||
}
|
||||
if (!wasStopped)
|
||||
addMessage("Torrent stopped: '" + sfile.getName() + "'");
|
||||
}
|
||||
return torrent;
|
||||
}
|
||||
/**
|
||||
* Stop the torrent and delete the torrent file itself, but leaving the data
|
||||
* behind.
|
||||
*/
|
||||
public void removeTorrent(String filename) {
|
||||
Snark torrent = stopTorrent(filename, true);
|
||||
if (torrent != null) {
|
||||
File torrentFile = new File(filename);
|
||||
torrentFile.delete();
|
||||
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private class DirMonitor implements Runnable {
|
||||
public void run() {
|
||||
try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {}
|
||||
// the first message was a "We are starting up in 1m"
|
||||
synchronized (_messages) {
|
||||
if (_messages.size() == 1)
|
||||
_messages.remove(0);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
File dir = getDataDir();
|
||||
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
|
||||
try {
|
||||
monitorTorrents(dir);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
}
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)"));
|
||||
}
|
||||
|
||||
private void monitorTorrents(File dir) {
|
||||
String fileNames[] = dir.list(TorrentFilenameFilter.instance());
|
||||
List foundNames = new ArrayList(0);
|
||||
if (fileNames != null) {
|
||||
for (int i = 0; i < fileNames.length; i++) {
|
||||
try {
|
||||
foundNames.add(new File(dir, fileNames[i]).getCanonicalPath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set existingNames = listTorrentFiles();
|
||||
// lets find new ones first...
|
||||
for (int i = 0; i < foundNames.size(); i++) {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connect())
|
||||
addTorrent((String)foundNames.get(i));
|
||||
else
|
||||
addMessage("Unable to connect to I2P");
|
||||
}
|
||||
}
|
||||
// now lets see which ones have been removed...
|
||||
for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (foundNames.contains(name)) {
|
||||
// known and still there. noop
|
||||
} else {
|
||||
// known, but removed. drop it
|
||||
stopTorrent(name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TorrentFilenameFilter implements FilenameFilter {
|
||||
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
|
||||
public static TorrentFilenameFilter instance() { return _filter; }
|
||||
public boolean accept(File dir, String name) {
|
||||
return (name != null) && (name.endsWith(".torrent"));
|
||||
}
|
||||
}
|
||||
}
|
||||
92
apps/i2psnark/java/src/org/klomp/snark/SnarkShutdown.java
Normal file
92
apps/i2psnark/java/src/org/klomp/snark/SnarkShutdown.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/* TrackerShutdown - Makes sure everything ends correctly when shutting down.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Makes sure everything ends correctly when shutting down.
|
||||
*/
|
||||
public class SnarkShutdown extends I2PThread
|
||||
{
|
||||
private final Storage storage;
|
||||
private final PeerCoordinator coordinator;
|
||||
private final ConnectionAcceptor acceptor;
|
||||
private final TrackerClient trackerclient;
|
||||
|
||||
private final ShutdownListener listener;
|
||||
|
||||
public SnarkShutdown(Storage storage,
|
||||
PeerCoordinator coordinator,
|
||||
ConnectionAcceptor acceptor,
|
||||
TrackerClient trackerclient,
|
||||
ShutdownListener listener)
|
||||
{
|
||||
this.storage = storage;
|
||||
this.coordinator = coordinator;
|
||||
this.acceptor = acceptor;
|
||||
this.trackerclient = trackerclient;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
Snark.debug("Shutting down...", Snark.NOTICE);
|
||||
|
||||
Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
|
||||
if (acceptor != null)
|
||||
acceptor.halt();
|
||||
|
||||
Snark.debug("Halting TrackerClient...", Snark.INFO);
|
||||
if (trackerclient != null)
|
||||
trackerclient.halt();
|
||||
|
||||
Snark.debug("Halting PeerCoordinator...", Snark.INFO);
|
||||
if (coordinator != null)
|
||||
coordinator.halt();
|
||||
|
||||
Snark.debug("Closing Storage...", Snark.INFO);
|
||||
if (storage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
storage.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
|
||||
throw new RuntimeException("b0rking");
|
||||
}
|
||||
}
|
||||
|
||||
// XXX - Should actually wait till done...
|
||||
try
|
||||
{
|
||||
Snark.debug("Waiting 5 seconds...", Snark.INFO);
|
||||
Thread.sleep(5*1000);
|
||||
}
|
||||
catch (InterruptedException ie) { /* ignored */ }
|
||||
|
||||
listener.shutdown();
|
||||
}
|
||||
}
|
||||
47
apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java
Normal file
47
apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/* StaticSnark - Main snark startup class for staticly linking with gcj.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
/**
|
||||
* Main snark startup class for staticly linking with gcj.
|
||||
* It references somee necessary classes that are normally loaded through
|
||||
* reflection.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class StaticSnark
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
// The GNU security provider is needed for SHA-1 MessageDigest checking.
|
||||
// So make sure it is available as a security provider.
|
||||
//Provider gnu = new gnu.java.security.provider.Gnu();
|
||||
//Security.addProvider(gnu);
|
||||
|
||||
// And finally call the normal starting point.
|
||||
Snark.main(args);
|
||||
}
|
||||
}
|
||||
584
apps/i2psnark/java/src/org/klomp/snark/Storage.java
Normal file
584
apps/i2psnark/java/src/org/klomp/snark/Storage.java
Normal file
@@ -0,0 +1,584 @@
|
||||
/* Storage - Class used to store and retrieve pieces.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import net.i2p.crypto.SHA1;
|
||||
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
*/
|
||||
public class Storage
|
||||
{
|
||||
private MetaInfo metainfo;
|
||||
private long[] lengths;
|
||||
private RandomAccessFile[] rafs;
|
||||
private String[] names;
|
||||
|
||||
private final StorageListener listener;
|
||||
|
||||
private final BitField bitfield;
|
||||
private int needed;
|
||||
|
||||
// XXX - Not always set correctly
|
||||
int piece_size;
|
||||
int pieces;
|
||||
|
||||
/** The default piece size. */
|
||||
private static int MIN_PIECE_SIZE = 256*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
private static long MAX_PIECES = 100*1024/20;
|
||||
|
||||
/**
|
||||
* Creates a new storage based on the supplied MetaInfo. This will
|
||||
* try to create and/or check all needed files in the MetaInfo.
|
||||
*
|
||||
* @exception IOException when creating and/or checking files fails.
|
||||
*/
|
||||
public Storage(MetaInfo metainfo, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
this.metainfo = metainfo;
|
||||
this.listener = listener;
|
||||
needed = metainfo.getPieces();
|
||||
bitfield = new BitField(needed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a storage from the existing file or directory together
|
||||
* with an appropriate MetaInfo file as can be announced on the
|
||||
* given announce String location.
|
||||
*/
|
||||
public Storage(File baseFile, String announce, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
this.listener = listener;
|
||||
|
||||
// Create names, rafs and lengths arrays.
|
||||
getFiles(baseFile);
|
||||
|
||||
long total = 0;
|
||||
ArrayList lengthsList = new ArrayList();
|
||||
for (int i = 0; i < lengths.length; i++)
|
||||
{
|
||||
long length = lengths[i];
|
||||
total += length;
|
||||
lengthsList.add(new Long(length));
|
||||
}
|
||||
|
||||
piece_size = MIN_PIECE_SIZE;
|
||||
pieces = (int) ((total - 1)/piece_size) + 1;
|
||||
while (pieces > MAX_PIECES)
|
||||
{
|
||||
piece_size = piece_size*2;
|
||||
pieces = (int) ((total - 1)/piece_size) +1;
|
||||
}
|
||||
|
||||
// Note that piece_hashes and the bitfield will be filled after
|
||||
// the MetaInfo is created.
|
||||
byte[] piece_hashes = new byte[20*pieces];
|
||||
bitfield = new BitField(pieces);
|
||||
needed = 0;
|
||||
|
||||
List files = new ArrayList();
|
||||
for (int i = 0; i < names.length; i++)
|
||||
{
|
||||
List file = new ArrayList();
|
||||
StringTokenizer st = new StringTokenizer(names[i], File.separator);
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
String part = st.nextToken();
|
||||
file.add(part);
|
||||
}
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
String name = baseFile.getName();
|
||||
if (files.size() == 1)
|
||||
{
|
||||
files = null;
|
||||
lengthsList = null;
|
||||
}
|
||||
|
||||
// Note that the piece_hashes are not correctly setup yet.
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
|
||||
lengthsList, piece_size, piece_hashes, total);
|
||||
|
||||
}
|
||||
|
||||
// Creates piece hases for a new storage.
|
||||
public void create() throws IOException
|
||||
{
|
||||
if (true) {
|
||||
fast_digestCreate();
|
||||
} else {
|
||||
orig_digestCreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void orig_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
MessageDigest digest = null;
|
||||
try
|
||||
{
|
||||
digest = MessageDigest.getInstance("SHA");
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
throw new InternalError(nsa.toString());
|
||||
}
|
||||
|
||||
byte[] piece_hashes = metainfo.getPieceHashes();
|
||||
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
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());
|
||||
}
|
||||
|
||||
private void fast_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
SHA1 digest = new SHA1();
|
||||
|
||||
byte[] piece_hashes = metainfo.getPieceHashes();
|
||||
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
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());
|
||||
}
|
||||
|
||||
private void getFiles(File base) throws IOException
|
||||
{
|
||||
ArrayList files = new ArrayList();
|
||||
addFiles(files, base);
|
||||
|
||||
int size = files.size();
|
||||
names = new String[size];
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
File f = (File)it.next();
|
||||
names[i] = f.getPath();
|
||||
lengths[i] = f.length();
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFiles(List l, File f)
|
||||
{
|
||||
if (!f.isDirectory())
|
||||
l.add(f);
|
||||
else
|
||||
{
|
||||
File[] files = f.listFiles();
|
||||
if (files == null)
|
||||
{
|
||||
Snark.debug("WARNING: Skipping '" + f
|
||||
+ "' not a normal file.", Snark.WARNING);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < files.length; i++)
|
||||
addFiles(l, files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MetaInfo associated with this Storage.
|
||||
*/
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many pieces are still missing from this storage.
|
||||
*/
|
||||
public int needed()
|
||||
{
|
||||
return needed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this storage contains all pieces if the MetaInfo.
|
||||
*/
|
||||
public boolean complete()
|
||||
{
|
||||
return needed == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BitField that tells which pieces this storage contains.
|
||||
* Do not change this since this is the current state of the storage.
|
||||
*/
|
||||
public BitField getBitField()
|
||||
{
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
*/
|
||||
public void check(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// Create base as file.
|
||||
Snark.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];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
if (base.exists() && !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");
|
||||
names[0] = base.getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create base as dir.
|
||||
Snark.debug("Creating/Checking directory: " + base, Snark.NOTICE);
|
||||
if (!base.mkdir() && !base.isDirectory())
|
||||
throw new IOException("Could not create directory " + base);
|
||||
|
||||
List ls = metainfo.getLengths();
|
||||
int size = files.size();
|
||||
long total = 0;
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
names = new String[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||
total += lengths[i];
|
||||
if (f.exists() && !f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
names[i] = f.getName();
|
||||
}
|
||||
|
||||
// Sanity check for metainfo file.
|
||||
long metalength = metainfo.getTotalLength();
|
||||
if (total != metalength)
|
||||
throw new IOException("File lengths do not add up "
|
||||
+ total + " != " + metalength);
|
||||
}
|
||||
checkCreateFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the give file name.
|
||||
*/
|
||||
private String filterName(String name)
|
||||
{
|
||||
// XXX - Is this enough?
|
||||
return name.replace(File.separatorChar, '_');
|
||||
}
|
||||
|
||||
private File createFileFromNames(File base, List names) throws IOException
|
||||
{
|
||||
File f = null;
|
||||
Iterator it = names.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = filterName((String)it.next());
|
||||
if (it.hasNext())
|
||||
{
|
||||
// Another dir in the hierarchy.
|
||||
f = new File(base, name);
|
||||
if (!f.mkdir() && !f.isDirectory())
|
||||
throw new IOException("Could not create directory " + f);
|
||||
base = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The final element (file) in the hierarchy.
|
||||
f = new File(base, name);
|
||||
if (!f.createNewFile() && !f.exists())
|
||||
throw new IOException("Could not create file " + f);
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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])
|
||||
{
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, length);
|
||||
resume = true; // XXX Could dynamicly check
|
||||
}
|
||||
else if (length == 0)
|
||||
allocateFile(i);
|
||||
else
|
||||
throw new IOException("File '" + names[i]
|
||||
+ "' exists, but has wrong length");
|
||||
}
|
||||
|
||||
// Check which pieces match and which don't
|
||||
if (resume)
|
||||
{
|
||||
pieces = metainfo.getPieces();
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
if (correctHash)
|
||||
{
|
||||
bitfield.set(i);
|
||||
needed--;
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, i, correctHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.storageAllChecked(this);
|
||||
if (needed <= 0)
|
||||
listener.storageCompleted(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void allocateFile(int nr) throws IOException
|
||||
{
|
||||
// XXX - Is this the best way to make sure we have enough space for
|
||||
// the whole file?
|
||||
listener.storageCreateFile(this, names[nr], lengths[nr]);
|
||||
final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
int i;
|
||||
for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
|
||||
{
|
||||
rafs[nr].write(zeros);
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, ZEROBLOCKSIZE);
|
||||
}
|
||||
int size = (int)(lengths[nr] - i*ZEROBLOCKSIZE);
|
||||
rafs[nr].write(zeros, 0, size);
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the Storage and makes sure that all RandomAccessFiles are
|
||||
* closed. The Storage is unusable after this.
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (rafs == null) return;
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
try {
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
I2PSnarkUtil.instance().debug("Error closing " + rafs[i], Snark.ERROR, ioe);
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null if
|
||||
* the storage doesn't contain the piece yet.
|
||||
*/
|
||||
public byte[] getPiece(int piece) throws IOException
|
||||
{
|
||||
if (!bitfield.get(piece))
|
||||
return null;
|
||||
|
||||
byte[] bs = new byte[metainfo.getPieceLength(piece)];
|
||||
getUncheckedPiece(piece, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the piece in the Storage if it is correct.
|
||||
*
|
||||
* @return true if the piece was correct (sha metainfo hash
|
||||
* matches), otherwise false.
|
||||
* @exception IOException when some storage related error occurs.
|
||||
*/
|
||||
public boolean putPiece(int piece, byte[] bs) throws IOException
|
||||
{
|
||||
// First check if the piece is correct.
|
||||
// If we were paranoia we could copy the array first.
|
||||
int length = bs.length;
|
||||
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, piece, correctHash);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
long start = piece * metainfo.getPieceLength(0);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
{
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
}
|
||||
|
||||
int written = 0;
|
||||
int off = 0;
|
||||
while (written < length)
|
||||
{
|
||||
int need = length - written;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].write(bs, off + written, len);
|
||||
}
|
||||
written += len;
|
||||
if (need - len > 0)
|
||||
{
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
listener.storageCompleted(this);
|
||||
// do we also need to close all of the files and reopen
|
||||
// them readonly?
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off)
|
||||
throws IOException
|
||||
{
|
||||
// XXX - copy/paste code from putPiece().
|
||||
long start = piece * metainfo.getPieceLength(0);
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
{
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
while (read < length)
|
||||
{
|
||||
int need = length - read;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, off + read, len);
|
||||
}
|
||||
read += len;
|
||||
if (need - len > 0)
|
||||
{
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
58
apps/i2psnark/java/src/org/klomp/snark/StorageListener.java
Normal file
58
apps/i2psnark/java/src/org/klomp/snark/StorageListener.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/* StorageListener.java - Interface used as callback when storage changes.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback used when Storage changes.
|
||||
*/
|
||||
public interface StorageListener
|
||||
{
|
||||
/**
|
||||
* Called when the storage creates a new file of a given length.
|
||||
*/
|
||||
void storageCreateFile(Storage storage, String name, long length);
|
||||
|
||||
/**
|
||||
* Called to indicate that length bytes have been allocated.
|
||||
*/
|
||||
void storageAllocated(Storage storage, long length);
|
||||
|
||||
/**
|
||||
* Called when storage is being checked and the num piece of that
|
||||
* total pieces has been checked. When the piece hash matches the
|
||||
* expected piece hash checked will be true, otherwise it will be
|
||||
* false.
|
||||
*/
|
||||
void storageChecked(Storage storage, int num, boolean checked);
|
||||
|
||||
/**
|
||||
* Called when all pieces in the storage have been checked. Does not
|
||||
* mean that the storage is complete, just that the state of the
|
||||
* storage is known.
|
||||
*/
|
||||
void storageAllChecked(Storage storage);
|
||||
|
||||
/**
|
||||
* Called the one time when the data is completely received and checked.
|
||||
*
|
||||
*/
|
||||
void storageCompleted(Storage storage);
|
||||
}
|
||||
317
apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
Normal file
317
apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
Normal file
@@ -0,0 +1,317 @@
|
||||
/* TrackerClient - Class that informs a tracker and gets new peers.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Informs metainfo tracker of events and gets new peers for peer
|
||||
* coordinator.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class TrackerClient extends I2PThread
|
||||
{
|
||||
private static final Log _log = new Log(TrackerClient.class);
|
||||
private static final String NO_EVENT = "";
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
private static final String STOPPED_EVENT = "stopped";
|
||||
|
||||
private final static int SLEEP = 5; // 5 minutes.
|
||||
|
||||
private final MetaInfo meta;
|
||||
private final PeerCoordinator coordinator;
|
||||
private final int port;
|
||||
|
||||
private boolean stop;
|
||||
private boolean started;
|
||||
|
||||
private long interval;
|
||||
private long lastRequestTime;
|
||||
|
||||
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
|
||||
{
|
||||
// Set unique name.
|
||||
super("TrackerClient-" + urlencode(coordinator.getID()));
|
||||
this.meta = meta;
|
||||
this.coordinator = coordinator;
|
||||
|
||||
this.port = 6881; //(port == -1) ? 9 : port;
|
||||
|
||||
stop = false;
|
||||
started = false;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (stop) throw new RuntimeException("Dont rerun me, create a copy");
|
||||
super.start();
|
||||
started = true;
|
||||
}
|
||||
|
||||
public boolean halted() { return stop; }
|
||||
public boolean started() { return started; }
|
||||
|
||||
/**
|
||||
* Interrupts this Thread to stop it.
|
||||
*/
|
||||
public void halt()
|
||||
{
|
||||
stop = true;
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
private boolean verifyConnected() {
|
||||
while (!stop && !I2PSnarkUtil.instance().connected()) {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
return !stop && I2PSnarkUtil.instance().connected();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// XXX - Support other IPs
|
||||
String announce = meta.getAnnounce(); //I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
|
||||
String infoHash = urlencode(meta.getInfoHash());
|
||||
String peerID = urlencode(coordinator.getID());
|
||||
|
||||
_log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash
|
||||
+ " xmitAnnounce: [" + announce + "]");
|
||||
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long left = coordinator.getLeft();
|
||||
|
||||
boolean completed = (left == 0);
|
||||
|
||||
try
|
||||
{
|
||||
if (!verifyConnected()) return;
|
||||
boolean started = false;
|
||||
while (!started)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send start.
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
STARTED_EVENT);
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if (!completed) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
started = true;
|
||||
coordinator.trackerProblems = null;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
}
|
||||
|
||||
if (!started && !stop)
|
||||
{
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
try
|
||||
{
|
||||
// Sleep one minutes...
|
||||
Thread.sleep(60*1000);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Random r = new Random();
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
if (!verifyConnected()) return;
|
||||
|
||||
uploaded = coordinator.getUploaded();
|
||||
downloaded = coordinator.getDownloaded();
|
||||
left = coordinator.getLeft();
|
||||
|
||||
// First time we got a complete download?
|
||||
String event;
|
||||
if (!completed && left == 0)
|
||||
{
|
||||
completed = true;
|
||||
event = COMPLETED_EVENT;
|
||||
}
|
||||
else
|
||||
event = NO_EVENT;
|
||||
|
||||
// Only do a request when necessary.
|
||||
if (event == COMPLETED_EVENT
|
||||
|| coordinator.needPeers()
|
||||
|| System.currentTimeMillis() > lastRequestTime + interval)
|
||||
{
|
||||
try
|
||||
{
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List ordered = new ArrayList(peers);
|
||||
Collections.shuffle(ordered);
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!verifyConnected()) return;
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID, uploaded,
|
||||
downloaded, left, STOPPED_EVENT);
|
||||
}
|
||||
catch(IOException ioe) { /* ignored */ }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private TrackerInfo doRequest(String announce, String infoHash,
|
||||
String peerID, long uploaded,
|
||||
long downloaded, long left, String event)
|
||||
throws IOException
|
||||
{
|
||||
String s = announce
|
||||
+ "?info_hash=" + infoHash
|
||||
+ "&peer_id=" + peerID
|
||||
+ "&port=" + port
|
||||
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString() + ".i2p"
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
File fetched = I2PSnarkUtil.instance().get(s);
|
||||
if (fetched == null) {
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(fetched);
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
if (failure != null)
|
||||
throw new IOException(failure);
|
||||
|
||||
interval = info.getInterval() * 1000;
|
||||
return info;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
fetched.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Very lazy byte[] to URL encoder. Just encodes everything, even
|
||||
* "normal" chars.
|
||||
*/
|
||||
static String urlencode(byte[] bs)
|
||||
{
|
||||
StringBuffer sb = new StringBuffer(bs.length*3);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
{
|
||||
int c = bs[i] & 0xFF;
|
||||
sb.append('%');
|
||||
if (c < 16)
|
||||
sb.append('0');
|
||||
sb.append(Integer.toHexString(c));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
128
apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
Normal file
128
apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/* TrackerInfo - Holds information returned by a tracker, mainly the peer list.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
public class TrackerInfo
|
||||
{
|
||||
private final String failure_reason;
|
||||
private final int interval;
|
||||
private final Set peers;
|
||||
|
||||
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this(new BDecoder(in), my_id, metainfo);
|
||||
}
|
||||
|
||||
public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this(be.bdecodeMap().getMap(), my_id, metainfo);
|
||||
}
|
||||
|
||||
public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
BEValue reason = (BEValue)m.get("failure reason");
|
||||
if (reason != null)
|
||||
{
|
||||
failure_reason = reason.getString();
|
||||
interval = -1;
|
||||
peers = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
failure_reason = null;
|
||||
BEValue beInterval = (BEValue)m.get("interval");
|
||||
if (beInterval == null)
|
||||
throw new InvalidBEncodingException("No interval given");
|
||||
else
|
||||
interval = beInterval.getInt();
|
||||
BEValue bePeers = (BEValue)m.get("peers");
|
||||
if (bePeers == null)
|
||||
throw new InvalidBEncodingException("No peer list");
|
||||
else
|
||||
peers = getPeers(bePeers.getList(), my_id, metainfo);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
return getPeers(new BDecoder(in), my_id, metainfo);
|
||||
}
|
||||
|
||||
public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
return getPeers(be.bdecodeList().getList(), my_id, metainfo);
|
||||
}
|
||||
|
||||
public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
Set peers = new HashSet(l.size());
|
||||
|
||||
Iterator it = l.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
PeerID peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
public Set getPeers()
|
||||
{
|
||||
return peers;
|
||||
}
|
||||
|
||||
public String getFailureReason()
|
||||
{
|
||||
return failure_reason;
|
||||
}
|
||||
|
||||
public int getInterval()
|
||||
{
|
||||
return interval;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
if (failure_reason != null)
|
||||
return "TrackerInfo[FAILED: " + failure_reason + "]";
|
||||
else
|
||||
return "TrackerInfo[interval=" + interval
|
||||
+ ", peers=" + peers + "]";
|
||||
}
|
||||
}
|
||||
355
apps/i2psnark/java/src/org/klomp/snark/bencode/BDecoder.java
Normal file
355
apps/i2psnark/java/src/org/klomp/snark/bencode/BDecoder.java
Normal file
@@ -0,0 +1,355 @@
|
||||
/* BDecoder - Converts an InputStream to BEValues.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Decodes a bencoded stream to <code>BEValue</code>s.
|
||||
*
|
||||
* A bencoded byte stream can represent byte arrays, numbers, lists and
|
||||
* maps (dictionaries).
|
||||
*
|
||||
* It currently contains a hack to indicate a name of a dictionary of
|
||||
* which a SHA-1 digest hash should be calculated (the hash over the
|
||||
* original bencoded bytes).
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org).
|
||||
*/
|
||||
public class BDecoder
|
||||
{
|
||||
// The InputStream to BDecode.
|
||||
private final InputStream in;
|
||||
|
||||
// The last indicator read.
|
||||
// Zero if unknown.
|
||||
// '0'..'9' indicates a byte[].
|
||||
// 'i' indicates an Number.
|
||||
// 'l' indicates a List.
|
||||
// 'd' indicates a Map.
|
||||
// 'e' indicates end of Number, List or Map (only used internally).
|
||||
// -1 indicates end of stream.
|
||||
// Call getNextIndicator to get the current value (will never return zero).
|
||||
private int indicator = 0;
|
||||
|
||||
// Used for ugly hack to get SHA hash over the metainfo info map
|
||||
private String special_map = "info";
|
||||
private boolean in_special_map = false;
|
||||
private final MessageDigest sha_digest;
|
||||
|
||||
// Ugly hack. Return the SHA has over bytes that make up the special map.
|
||||
public byte[] get_special_map_digest()
|
||||
{
|
||||
byte[] result = sha_digest.digest();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ugly hack. Name defaults to "info".
|
||||
public void set_special_map_name(String name)
|
||||
{
|
||||
special_map = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalizes a new BDecoder. Nothing is read from the given
|
||||
* <code>InputStream</code> yet.
|
||||
*/
|
||||
public BDecoder(InputStream in)
|
||||
{
|
||||
this.in = in;
|
||||
// XXX - Used for ugly hack.
|
||||
try
|
||||
{
|
||||
sha_digest = MessageDigest.getInstance("SHA");
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
throw new InternalError(nsa.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new BDecoder and immediatly decodes the first value it
|
||||
* sees.
|
||||
*
|
||||
* @return The first BEValue on the stream or null when the stream
|
||||
* has ended.
|
||||
*
|
||||
* @exception InvalidBEncoding when the stream doesn't start with a
|
||||
* bencoded value or the stream isn't a bencoded stream at all.
|
||||
* @exception IOException when somthing bad happens with the stream
|
||||
* to read from.
|
||||
*/
|
||||
public static BEValue bdecode(InputStream in) throws IOException
|
||||
{
|
||||
return new BDecoder(in).bdecode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns what the next bencoded object will be on the stream or -1
|
||||
* when the end of stream has been reached. Can return something
|
||||
* unexpected (not '0' .. '9', 'i', 'l' or 'd') when the stream
|
||||
* isn't bencoded.
|
||||
*
|
||||
* This might or might not read one extra byte from the stream.
|
||||
*/
|
||||
public int getNextIndicator() throws IOException
|
||||
{
|
||||
if (indicator == 0)
|
||||
{
|
||||
indicator = in.read();
|
||||
// XXX - Used for ugly hack
|
||||
if (in_special_map) sha_digest.update((byte)indicator);
|
||||
}
|
||||
return indicator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next indicator and returns either null when the stream
|
||||
* has ended or bdecodes the rest of the stream and returns the
|
||||
* appropriate BEValue encoded object.
|
||||
*/
|
||||
public BEValue bdecode() throws IOException
|
||||
{
|
||||
indicator = getNextIndicator();
|
||||
if (indicator == -1)
|
||||
return null;
|
||||
|
||||
if (indicator >= '0' && indicator <= '9')
|
||||
return bdecodeBytes();
|
||||
else if (indicator == 'i')
|
||||
return bdecodeNumber();
|
||||
else if (indicator == 'l')
|
||||
return bdecodeList();
|
||||
else if (indicator == 'd')
|
||||
return bdecodeMap();
|
||||
else
|
||||
throw new InvalidBEncodingException
|
||||
("Unknown indicator '" + indicator + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a byte array. If it is not a bencoded byte array it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeBytes() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
int num = c - '0';
|
||||
if (num < 0 || num > 9)
|
||||
throw new InvalidBEncodingException("Number expected, not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
c = read();
|
||||
int i = c - '0';
|
||||
while (i >= 0 && i <= 9)
|
||||
{
|
||||
// XXX - This can overflow!
|
||||
num = num*10 + i;
|
||||
c = read();
|
||||
i = c - '0';
|
||||
}
|
||||
|
||||
if (c != ':')
|
||||
throw new InvalidBEncodingException("Colon expected, not '"
|
||||
+ (char)c + "'");
|
||||
|
||||
return new BEValue(read(num));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a number. If it is not a number it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeNumber() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'i')
|
||||
throw new InvalidBEncodingException("Expected 'i', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
c = read();
|
||||
if (c == '0')
|
||||
{
|
||||
c = read();
|
||||
if (c == 'e')
|
||||
return new BEValue(BigInteger.ZERO);
|
||||
else
|
||||
throw new InvalidBEncodingException("'e' expected after zero,"
|
||||
+ " not '" + (char)c + "'");
|
||||
}
|
||||
|
||||
// XXX - We don't support more the 255 char big integers
|
||||
char[] chars = new char[256];
|
||||
int off = 0;
|
||||
|
||||
if (c == '-')
|
||||
{
|
||||
c = read();
|
||||
if (c == '0')
|
||||
throw new InvalidBEncodingException("Negative zero not allowed");
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
}
|
||||
|
||||
if (c < '1' || c > '9')
|
||||
throw new InvalidBEncodingException("Invalid Integer start '"
|
||||
+ (char)c + "'");
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
|
||||
c = read();
|
||||
int i = c - '0';
|
||||
while(i >= 0 && i <= 9)
|
||||
{
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
c = read();
|
||||
i = c - '0';
|
||||
}
|
||||
|
||||
if (c != 'e')
|
||||
throw new InvalidBEncodingException("Integer should end with 'e'");
|
||||
|
||||
String s = new String(chars, 0, off);
|
||||
return new BEValue(new BigInteger(s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a list. If it is not a list it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeList() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'l')
|
||||
throw new InvalidBEncodingException("Expected 'l', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
List result = new ArrayList();
|
||||
c = getNextIndicator();
|
||||
while (c != 'e')
|
||||
{
|
||||
result.add(bdecode());
|
||||
c = getNextIndicator();
|
||||
}
|
||||
indicator = 0;
|
||||
|
||||
return new BEValue(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a map (dictonary). If it is not a map it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeMap() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'd')
|
||||
throw new InvalidBEncodingException("Expected 'd', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
Map result = new HashMap();
|
||||
c = getNextIndicator();
|
||||
while (c != 'e')
|
||||
{
|
||||
// Dictonary keys are always strings.
|
||||
String key = bdecode().getString();
|
||||
|
||||
// XXX ugly hack
|
||||
boolean special = special_map.equals(key);
|
||||
if (special)
|
||||
in_special_map = true;
|
||||
|
||||
BEValue value = bdecode();
|
||||
result.put(key, value);
|
||||
|
||||
// XXX ugly hack continued
|
||||
if (special)
|
||||
in_special_map = false;
|
||||
|
||||
c = getNextIndicator();
|
||||
}
|
||||
indicator = 0;
|
||||
|
||||
return new BEValue(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next byte read from the InputStream (as int).
|
||||
* Throws EOFException if InputStream.read() returned -1.
|
||||
*/
|
||||
private int read() throws IOException
|
||||
{
|
||||
int c = in.read();
|
||||
if (c == -1)
|
||||
throw new EOFException();
|
||||
if (in_special_map) sha_digest.update((byte)c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte[] containing length valid bytes starting at offset
|
||||
* zero. Throws EOFException if InputStream.read() returned -1
|
||||
* before all requested bytes could be read. Note that the byte[]
|
||||
* returned might be bigger then requested but will only contain
|
||||
* length valid bytes. The returned byte[] will be reused when this
|
||||
* method is called again.
|
||||
*/
|
||||
private byte[] read(int length) throws IOException
|
||||
{
|
||||
byte[] result = new byte[length];
|
||||
|
||||
int read = 0;
|
||||
while (read < length)
|
||||
{
|
||||
int i = in.read(result, read, length - read);
|
||||
if (i == -1)
|
||||
throw new EOFException();
|
||||
read += i;
|
||||
}
|
||||
|
||||
if (in_special_map) sha_digest.update(result, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
193
apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
Normal file
193
apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/* BEValue - Holds different types that a bencoded byte array can represent.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds different types that a bencoded byte array can represent.
|
||||
* You need to call the correct get method to get the correct java
|
||||
* type object. If the BEValue wasn't actually of the requested type
|
||||
* you will get a InvalidBEncodingException.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class BEValue
|
||||
{
|
||||
// This is either a byte[], Number, List or Map.
|
||||
private final Object value;
|
||||
|
||||
public BEValue(byte[] value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BEValue(Number value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BEValue(List value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BEValue(Map value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a String. This operation only succeeds
|
||||
* when the BEValue is a byte[], otherwise it will throw a
|
||||
* InvalidBEncodingException. The byte[] will be interpreted as
|
||||
* UTF-8 encoded characters.
|
||||
*/
|
||||
public String getString() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return new String(getBytes(), "UTF-8");
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
throw new InternalError(uee.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a byte[]. This operation only succeeds
|
||||
* when the BEValue is actually a byte[], otherwise it will throw a
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public byte[] getBytes() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (byte[])value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a Number. This operation only succeeds
|
||||
* when the BEValue is actually a Number, otherwise it will throw a
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public Number getNumber() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Number)value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as int. This operation only succeeds when
|
||||
* the BEValue is actually a Number, otherwise it will throw a
|
||||
* InvalidBEncodingException. The returned int is the result of
|
||||
* <code>Number.intValue()</code>.
|
||||
*/
|
||||
public int getInt() throws InvalidBEncodingException
|
||||
{
|
||||
return getNumber().intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as long. This operation only succeeds when
|
||||
* the BEValue is actually a Number, otherwise it will throw a
|
||||
* InvalidBEncodingException. The returned long is the result of
|
||||
* <code>Number.longValue()</code>.
|
||||
*/
|
||||
public long getLong() throws InvalidBEncodingException
|
||||
{
|
||||
return getNumber().longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a List of BEValues. This operation only
|
||||
* succeeds when the BEValue is actually a List, otherwise it will
|
||||
* throw a InvalidBEncodingException.
|
||||
*/
|
||||
public List getList() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (List)value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a Map of BEValue keys and BEValue
|
||||
* values. This operation only succeeds when the BEValue is actually
|
||||
* a Map, otherwise it will throw a InvalidBEncodingException.
|
||||
*/
|
||||
public Map getMap() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Map)value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** return the untyped value */
|
||||
public Object getValue() { return value; }
|
||||
|
||||
public String toString()
|
||||
{
|
||||
String valueString;
|
||||
if (value instanceof byte[])
|
||||
{
|
||||
byte[] bs = (byte[])value;
|
||||
// XXX - Stupid heuristic...
|
||||
if (bs.length <= 12)
|
||||
valueString = new String(bs);
|
||||
else
|
||||
valueString = "bytes:" + bs.length;
|
||||
}
|
||||
else
|
||||
valueString = value.toString();
|
||||
|
||||
return "BEValue[" + valueString + "]";
|
||||
}
|
||||
}
|
||||
193
apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java
Normal file
193
apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/* BDecoder - Converts an InputStream to BEValues.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BEncoder
|
||||
{
|
||||
|
||||
public static byte[] bencode(Object o) throws IllegalArgumentException
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(o, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(Object o, OutputStream out)
|
||||
throws IOException, IllegalArgumentException
|
||||
{
|
||||
if (o instanceof String)
|
||||
bencode((String)o, out);
|
||||
else if (o instanceof byte[])
|
||||
bencode((byte[])o, out);
|
||||
else if (o instanceof Number)
|
||||
bencode((Number)o, out);
|
||||
else if (o instanceof List)
|
||||
bencode((List)o, out);
|
||||
else if (o instanceof Map)
|
||||
bencode((Map)o, out);
|
||||
else if (o instanceof BEValue)
|
||||
bencode(((BEValue)o).getValue(), out);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot bencode: " + o.getClass());
|
||||
}
|
||||
|
||||
public static byte[] bencode(String s)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(s, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(String s, OutputStream out) throws IOException
|
||||
{
|
||||
byte[] bs = s.getBytes("UTF-8");
|
||||
bencode(bs, out);
|
||||
}
|
||||
|
||||
public static byte[] bencode(Number n)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(n, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(Number n, OutputStream out) throws IOException
|
||||
{
|
||||
out.write('i');
|
||||
String s = n.toString();
|
||||
out.write(s.getBytes("UTF-8"));
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
public static byte[] bencode(List l)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(l, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(List l, OutputStream out) throws IOException
|
||||
{
|
||||
out.write('l');
|
||||
Iterator it = l.iterator();
|
||||
while (it.hasNext())
|
||||
bencode(it.next(), out);
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
public static byte[] bencode(byte[] bs)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(bs, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(byte[] bs, OutputStream out) throws IOException
|
||||
{
|
||||
String l = Integer.toString(bs.length);
|
||||
out.write(l.getBytes("UTF-8"));
|
||||
out.write(':');
|
||||
out.write(bs);
|
||||
}
|
||||
|
||||
public static byte[] bencode(Map m)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(m, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(Map m, OutputStream out) throws IOException
|
||||
{
|
||||
out.write('d');
|
||||
|
||||
// Keys must be sorted. XXX - But is this the correct order?
|
||||
Set s = m.keySet();
|
||||
List l = new ArrayList(s);
|
||||
Collections.sort(l);
|
||||
|
||||
Iterator it = l.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
// Keys must be Strings.
|
||||
String key = (String)it.next();
|
||||
Object value = m.get(key);
|
||||
bencode(key, out);
|
||||
bencode(value, out);
|
||||
}
|
||||
|
||||
out.write('e');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* InvalidBEncodingException - Thrown when a bencoded stream is corrupted.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception thrown when a bencoded stream is corrupted.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class InvalidBEncodingException extends IOException
|
||||
{
|
||||
public InvalidBEncodingException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
620
apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
Normal file
620
apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
Normal file
@@ -0,0 +1,620 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
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.Log;
|
||||
import org.klomp.snark.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class I2PSnarkServlet extends HttpServlet {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private SnarkManager _manager;
|
||||
private static long _nonce;
|
||||
|
||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||
|
||||
public void init(ServletConfig cfg) throws ServletException {
|
||||
super.init(cfg);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(I2PSnarkServlet.class);
|
||||
_nonce = _context.random().nextLong();
|
||||
_manager = SnarkManager.instance();
|
||||
String configFile = _context.getProperty(PROP_CONFIG_FILE);
|
||||
if ( (configFile == null) || (configFile.trim().length() <= 0) )
|
||||
configFile = "i2psnark.config";
|
||||
_manager.loadConfig(configFile);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
String nonce = req.getParameter("nonce");
|
||||
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
|
||||
processRequest(req);
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.write(HEADER_BEGIN);
|
||||
// we want it to go to the base URI so we don't refresh with some funky action= value
|
||||
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + "\">\n");
|
||||
out.write(HEADER);
|
||||
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td width=\"5%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("I2PSnark<br />\n");
|
||||
out.write("<a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("</td><td width=\"95%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
List msgs = _manager.getMessages();
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = (String)msgs.get(i);
|
||||
out.write(msg + "\n");
|
||||
}
|
||||
out.write("</pre></td></tr></table>\n");
|
||||
|
||||
out.write(TABLE_HEADER);
|
||||
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
displaySnark(out, snark, uri, i);
|
||||
}
|
||||
if (snarks.size() <= 0) {
|
||||
out.write(TABLE_EMPTY);
|
||||
}
|
||||
|
||||
out.write(TABLE_FOOTER);
|
||||
writeAddForm(out, req);
|
||||
if (false) // seeding needs to register the torrent first (boo, hiss)
|
||||
writeSeedForm(out, req);
|
||||
writeConfigForm(out, req);
|
||||
out.write(FOOTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do what they ask, adding messages to _manager.addMessage as necessary
|
||||
*/
|
||||
private void processRequest(HttpServletRequest req) {
|
||||
String action = req.getParameter("action");
|
||||
if (action == null) {
|
||||
// noop
|
||||
} else if ("Add torrent".equals(action)) {
|
||||
String newFile = req.getParameter("newFile");
|
||||
String newURL = req.getParameter("newURL");
|
||||
File f = null;
|
||||
if ( (newFile != null) && (newFile.trim().length() > 0) )
|
||||
f = new File(newFile.trim());
|
||||
if ( (f != null) && (!f.exists()) ) {
|
||||
_manager.addMessage("Torrent file " + newFile +" does not exist");
|
||||
}
|
||||
if ( (f != null) && (f.exists()) ) {
|
||||
File local = new File(_manager.getDataDir(), f.getName());
|
||||
String canonical = null;
|
||||
try {
|
||||
canonical = local.getCanonicalPath();
|
||||
|
||||
if (local.exists()) {
|
||||
if (_manager.getTorrent(canonical) != null)
|
||||
_manager.addMessage("Torrent already running: " + newFile);
|
||||
else
|
||||
_manager.addMessage("Torrent already in the queue: " + newFile);
|
||||
} else {
|
||||
boolean ok = FileUtil.copy(f.getAbsolutePath(), local.getAbsolutePath(), true);
|
||||
if (ok) {
|
||||
_manager.addMessage("Copying torrent to " + local.getAbsolutePath());
|
||||
_manager.addTorrent(canonical);
|
||||
} else {
|
||||
_manager.addMessage("Unable to copy the torrent to " + local.getAbsolutePath() + " from " + f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} 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(newURL), "Fetch and add");
|
||||
fetch.start();
|
||||
} else {
|
||||
// no file or URL specified
|
||||
}
|
||||
} else if ("Stop".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
_manager.stopTorrent(name, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Start".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
snark.startTorrent();
|
||||
_manager.addMessage("Starting up torrent " + name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Remove".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
_manager.stopTorrent(name, true);
|
||||
// should we delete the torrent file?
|
||||
// yeah, need to, otherwise it'll get autoadded again (at the moment
|
||||
File f = new File(name);
|
||||
f.delete();
|
||||
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Delete".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
_manager.stopTorrent(name, true);
|
||||
File f = new File(name);
|
||||
f.delete();
|
||||
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
|
||||
List files = snark.meta.getFiles();
|
||||
String dataFile = snark.meta.getName();
|
||||
for (int i = 0; files != null && i < files.size(); i++) {
|
||||
File df = new File(_manager.getDataDir(), (String)files.get(i));
|
||||
boolean deleted = FileUtil.rmdir(df, false);
|
||||
if (deleted)
|
||||
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data dir could not be deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
if (dataFile != null) {
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
boolean deleted = f.delete();
|
||||
if (deleted)
|
||||
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Save configuration".equals(action)) {
|
||||
String dataDir = req.getParameter("dataDir");
|
||||
boolean autoStart = req.getParameter("autoStart") != null;
|
||||
String seedPct = req.getParameter("seedPct");
|
||||
String eepHost = req.getParameter("eepHost");
|
||||
String eepPort = req.getParameter("eepPort");
|
||||
String i2cpHost = req.getParameter("i2cpHost");
|
||||
String i2cpPort = req.getParameter("i2cpPort");
|
||||
String i2cpOpts = req.getParameter("i2cpOpts");
|
||||
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts);
|
||||
} else if ("Create torrent".equals(action)) {
|
||||
String baseData = req.getParameter("baseFile");
|
||||
if (baseData != null) {
|
||||
File baseFile = new File(_manager.getDataDir(), baseData);
|
||||
String announceURL = req.getParameter("announceURL");
|
||||
String announceURLOther = req.getParameter("announceURLOther");
|
||||
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
|
||||
announceURL = announceURLOther;
|
||||
|
||||
if (baseFile.exists()) {
|
||||
try {
|
||||
Storage s = new Storage(baseFile, announceURL, null);
|
||||
s.create();
|
||||
MetaInfo info = s.getMetaInfo();
|
||||
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
|
||||
if (torrentFile.exists())
|
||||
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
|
||||
FileOutputStream out = new FileOutputStream(torrentFile);
|
||||
out.write(info.getTorrentData());
|
||||
out.close();
|
||||
_manager.addMessage("Torrent created for " + baseFile.getName() + ": " + torrentFile.getAbsolutePath());
|
||||
// now fire it up and seed away!
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath());
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistant data: " + baseFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FetchAndAdd implements Runnable {
|
||||
private String _url;
|
||||
public FetchAndAdd(String url) {
|
||||
_url = url;
|
||||
}
|
||||
public void run() {
|
||||
_url = _url.trim();
|
||||
File file = I2PSnarkUtil.instance().get(_url, false);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage("Torrent fetched from " + _url);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
MetaInfo info = new MetaInfo(in);
|
||||
String name = info.getName();
|
||||
name = name.replace('/', '_');
|
||||
name = name.replace('\\', '_');
|
||||
name = name.replace('&', '+');
|
||||
name = name.replace('\'', '_');
|
||||
name = name.replace('"', '_');
|
||||
name = name.replace('`', '_');
|
||||
name = name + ".torrent";
|
||||
File torrentFile = new File(_manager.getDataDir(), name);
|
||||
|
||||
String canonical = torrentFile.getCanonicalPath();
|
||||
|
||||
if (torrentFile.exists()) {
|
||||
if (_manager.getTorrent(canonical) != null)
|
||||
_manager.addMessage("Torrent already running: " + name);
|
||||
else
|
||||
_manager.addMessage("Torrent already in the queue: " + name);
|
||||
} else {
|
||||
FileUtil.copy(file.getAbsolutePath(), canonical, true);
|
||||
_manager.addTorrent(canonical);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Torrent was not retrieved from " + _url);
|
||||
}
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List getSortedSnarks(HttpServletRequest req) {
|
||||
Set files = _manager.listTorrentFiles();
|
||||
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
|
||||
ArrayList rv = new ArrayList(fileNames.size());
|
||||
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if (snark != null)
|
||||
rv.add(snark);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
|
||||
String filename = snark.torrent;
|
||||
File f = new File(filename);
|
||||
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
|
||||
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
|
||||
long total = snark.meta.getTotalLength();
|
||||
long remaining = snark.storage.needed() * snark.meta.getPieceLength(0);
|
||||
if (remaining > total)
|
||||
remaining = total;
|
||||
int totalBps = 4096; // should probably grab this from the snark...
|
||||
long remainingSeconds = remaining / totalBps;
|
||||
long uploaded = snark.coordinator.getUploaded();
|
||||
|
||||
boolean isRunning = !snark.stopped;
|
||||
boolean isValid = snark.meta != null;
|
||||
boolean singleFile = snark.meta.getFiles() == null;
|
||||
|
||||
String err = snark.coordinator.trackerProblems;
|
||||
int curPeers = snark.coordinator.getPeerCount();
|
||||
int knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
|
||||
String statusString = "Unknown";
|
||||
if (err != null) {
|
||||
if (isRunning)
|
||||
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "TrackerErr (" + err + ")";
|
||||
} else if (remaining <= 0) {
|
||||
if (isRunning)
|
||||
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Complete";
|
||||
} else {
|
||||
if (isRunning)
|
||||
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Stopped";
|
||||
}
|
||||
|
||||
String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
|
||||
out.write("<tr class=\"" + rowClass + "\">");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write(statusString + "</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
|
||||
|
||||
if (remaining == 0)
|
||||
out.write("<a href=\"file:///" + _manager.getDataDir().getAbsolutePath() + File.separatorChar + snark.meta.getName()
|
||||
+ "\" title=\"Download the completed file\">");
|
||||
out.write(filename);
|
||||
if (remaining == 0)
|
||||
out.write("</a>");
|
||||
out.write("</td>\n\t");
|
||||
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
// lets hold off on the ETA until we have rates sorted...
|
||||
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
|
||||
} else {
|
||||
out.write(formatSize(total)); // 3GB
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
|
||||
+ "\">" + formatSize(uploaded) + "</td>\n\t");
|
||||
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
|
||||
//out.write("n/a"); //2KBps/12KBps/4KBps
|
||||
//out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
|
||||
if (isRunning) {
|
||||
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Stop the torrent\">Stop</a>");
|
||||
} else {
|
||||
if (isValid)
|
||||
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Start the torrent\">Start</a> ");
|
||||
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
|
||||
out.write("<a href=\"" + uri + "?action=Delete&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
|
||||
}
|
||||
out.write("</td>\n</tr>\n");
|
||||
}
|
||||
|
||||
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://";
|
||||
String newFile = req.getParameter("newFile");
|
||||
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
|
||||
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
|
||||
out.write("<span class=\"snarkAddInfo\">Alternately, you can copy .torrent files to " + _manager.getDataDir().getAbsolutePath() + "<br />\n");
|
||||
out.write("Removing that .torrent file will cause the torrent to stop.<br /></span>\n");
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private static final String DEFAULT_TRACKERS[] = {
|
||||
"Postman's tracker", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php",
|
||||
"Orion's tracker", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt",
|
||||
"The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
|
||||
};
|
||||
private void writeSeedForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String baseFile = req.getParameter("baseFile");
|
||||
if (baseFile == null)
|
||||
baseFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("Data to seed: <input type=\"text\" name=\"baseFile\" size=\"50\" value=\"" + baseFile
|
||||
+ "\" title=\"File within " + _manager.getDataDir().getAbsolutePath() + " to seed\" /><br />\n");
|
||||
out.write("Tracker: <select name=\"announceURL\"><option value=\"\">Select a tracker</option>\n");
|
||||
for (int i = 0; i + 1 < DEFAULT_TRACKERS.length; i += 2)
|
||||
out.write("\t<option value=\"" + DEFAULT_TRACKERS[i+1] + "\">" + DEFAULT_TRACKERS[i] + "</option>\n");
|
||||
out.write("</select><br />\n");
|
||||
out.write(" or: ");
|
||||
out.write("<input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
|
||||
"title=\"Custom tracker URL\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Create torrent\" name=\"action\" />\n");
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String dataDir = _manager.getDataDir().getAbsolutePath();
|
||||
boolean autoStart = _manager.shouldAutoStart();
|
||||
int seedPct = 0;
|
||||
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<span class=\"snarkConfig\"><hr />\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Configuration:</span><br />\n");
|
||||
out.write("Data directory: <input type=\"text\" size=\"40\" name=\"dataDir\" value=\"" + dataDir + "\" ");
|
||||
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /><br />\n");
|
||||
out.write("Auto start: <input type=\"checkbox\" name=\"autoStart\" value=\"true\" "
|
||||
+ (autoStart ? "checked " : "")
|
||||
+ "title=\"If true, automatically start torrents that are added\" />");
|
||||
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
|
||||
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
|
||||
//out.write("<br />\n");
|
||||
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
|
||||
if (seedPct <= 0)
|
||||
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
|
||||
else
|
||||
out.write("<option value=\"0\">Unlimited</option>\n\t");
|
||||
if (seedPct == 100)
|
||||
out.write("<option value=\"100\" selected=\"true\">100%</option>\n\t");
|
||||
else
|
||||
out.write("<option value=\"100\">100%</option>\n\t");
|
||||
if (seedPct == 150)
|
||||
out.write("<option value=\"150\" selected=\"true\">150%</option>\n\t");
|
||||
else
|
||||
out.write("<option value=\"150\">150%</option>\n\t");
|
||||
out.write("</select><br />\n");
|
||||
|
||||
//out.write("<hr />\n");
|
||||
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" /><br />\n");
|
||||
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
|
||||
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" /> <br />\n");
|
||||
StringBuffer opts = new StringBuffer(64);
|
||||
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
|
||||
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = (String)options.get(key);
|
||||
opts.append(key).append('=').append(val).append(' ');
|
||||
}
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"40\" value=\""
|
||||
+ opts.toString() + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
|
||||
out.write("</span>\n");
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
private String formatSize(long bytes) {
|
||||
if (bytes < 5*1024)
|
||||
return bytes + "B";
|
||||
else if (bytes < 5*1024*1024)
|
||||
return (bytes/1024) + "KB";
|
||||
else if (bytes < 5*1024*1024*1024l)
|
||||
return (bytes/(1024*1024)) + "MB";
|
||||
else
|
||||
return (bytes/(1024*1024*1024)) + "GB";
|
||||
}
|
||||
|
||||
private static final String HEADER_BEGIN = "<html>\n" +
|
||||
"<head>\n" +
|
||||
"<title>I2PSnark - anonymous bittorrent</title>\n";
|
||||
|
||||
private static final String HEADER = "<style>\n" +
|
||||
"body {\n" +
|
||||
" background-color: #C7CFB4;\n" +
|
||||
"}\n" +
|
||||
".snarkTitle {\n" +
|
||||
" text-align: left;\n" +
|
||||
" float: left;\n" +
|
||||
" margin: 0px 0px 5px 5px;\n" +
|
||||
" display: inline;\n" +
|
||||
" font-size: 16pt;\n" +
|
||||
" font-weight: bold;\n" +
|
||||
"}\n" +
|
||||
".snarkRefresh {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
".snarkMessages {\n" +
|
||||
" border: none;\n" +
|
||||
" background-color: #CECFC6;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
" font-weight: 100;\n" +
|
||||
" width: 100%;\n" +
|
||||
" text-align: left;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" border: 0px;\n" +
|
||||
" padding: 5px;\n" +
|
||||
" border-width: 0px;\n" +
|
||||
" border-spacing: 0px;\n" +
|
||||
"}\n" +
|
||||
"table {\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" border: 0px;\n" +
|
||||
" padding: 0px;\n" +
|
||||
" border-width: 0px;\n" +
|
||||
" border-spacing: 0px;\n" +
|
||||
"}\n" +
|
||||
"th {\n" +
|
||||
" background-color: #C7D5D5;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentEven {\n" +
|
||||
" background-color: #E7E7E7;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentOdd {\n" +
|
||||
" background-color: #DDDDCC;\n" +
|
||||
"}\n" +
|
||||
".snarkNewTorrent {\n" +
|
||||
" font-size: 12pt;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" background-color: #ADAE9;\n" +
|
||||
"}\n" +
|
||||
".snarkAddInfo {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
".snarkConfigTitle {\n" +
|
||||
" font-size: 12pt;\n" +
|
||||
" font-weight: bold;\n" +
|
||||
"}\n" +
|
||||
".snarkConfig {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
"</style>\n" +
|
||||
"</head>\n" +
|
||||
"<body>\n";
|
||||
|
||||
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
|
||||
"<thead>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
|
||||
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</thead>\n";
|
||||
|
||||
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"left\"" +
|
||||
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
|
||||
|
||||
private static final String TABLE_FOOTER = "</table>\n";
|
||||
|
||||
private static final String FOOTER = "</body></html>";
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.util.FileUtil;
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
public class RunStandalone {
|
||||
private Server _server;
|
||||
|
||||
static {
|
||||
System.setProperty("org.mortbay.http.Version.paranoid", "true");
|
||||
System.setProperty("org.mortbay.xml.XmlParser.NotValidating", "true");
|
||||
}
|
||||
|
||||
private RunStandalone(String args[]) {}
|
||||
|
||||
public static void main(String args[]) {
|
||||
RunStandalone runner = new RunStandalone(args);
|
||||
runner.start();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
File workDir = new File("work");
|
||||
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
|
||||
if (!workDirRemoved)
|
||||
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
|
||||
boolean workDirCreated = workDir.mkdirs();
|
||||
if (!workDirCreated)
|
||||
System.err.println("ERROR: Unable to create Jetty temporary work directory");
|
||||
|
||||
try {
|
||||
_server = new Server("jetty-i2psnark.xml");
|
||||
_server.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
_server.stop();
|
||||
} catch (InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
114
apps/i2psnark/jetty-i2psnark.xml
Normal file
114
apps/i2psnark/jetty-i2psnark.xml
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
|
||||
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Jetty Server -->
|
||||
<!-- =============================================================== -->
|
||||
<Configure class="org.mortbay.jetty.Server">
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Listeners -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add and configure a HTTP listener to port 8080 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SocketListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.util.InetAddrPort">
|
||||
<Set name="host">127.0.0.1</Set>
|
||||
<Set name="port">8002</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">10</Set>
|
||||
<Set name="MaxIdleTimeMs">30000</Set>
|
||||
<Set name="LowResourcePersistTimeMs">1000</Set>
|
||||
<Set name="ConfidentialPort">8443</Set>
|
||||
<Set name="IntegralPort">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a HTTPS SSL listener on port 8443 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- UNCOMMENT TO ACTIVATE
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SunJsseListener">
|
||||
<Set name="Port">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
<Set name="Keystore"><SystemProperty name="jetty.home" default="."/>/etc/demokeystore</Set>
|
||||
<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
|
||||
<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
|
||||
<Set name="NonPersistentUserAgent">MSIE 5</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a AJP13 listener on port 8009 -->
|
||||
<!-- This protocol can be used with mod_jk in apache, IIS etc. -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!--
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.ajp.AJP13Listener">
|
||||
<Set name="PoolName">ajp</Set>
|
||||
<Set name="Port">8009</Set>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">20</Set>
|
||||
<Set name="MaxIdleTimeMs">0</Set>
|
||||
<Set name="confidentialPort">443</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Contexts -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a all web application within the webapps directory. -->
|
||||
<!-- + No virtual host specified -->
|
||||
<!-- + Look in the webapps directory relative to jetty.home or . -->
|
||||
<!-- + Use the default webdefault.xml in jetty's install -->
|
||||
<!-- + Upack the war file -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Set name="rootWebApp">i2psnark</Set>
|
||||
<Call name="addWebApplication">
|
||||
<Arg>/</Arg>
|
||||
<Arg>webapps/i2psnark.war</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Log -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="RequestLog">
|
||||
<New class="org.mortbay.http.NCSARequestLog">
|
||||
<Arg>./logs/yyyy_mm_dd.i2psnark-request.log</Arg>
|
||||
<Set name="retainDays">90</Set>
|
||||
<Set name="append">true</Set>
|
||||
<Set name="extended">false</Set>
|
||||
<Set name="buffered">false</Set>
|
||||
<Set name="LogTimeZone">GMT</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Other Server Options -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="requestsPerGC">2000</Set>
|
||||
<Set name="statsOn">false</Set>
|
||||
|
||||
</Configure>
|
||||
6
apps/i2psnark/readme-standalone.txt
Normal file
6
apps/i2psnark/readme-standalone.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
To run I2PSnark from the command line, run "java -jar lib/i2psnark.jar", but
|
||||
to run it with the web UI, run "java -jar launch-i2psnark.jar". I2PSnark is
|
||||
GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
|
||||
(http://www.i2p.net/) within a webserver (such as the bundled Jetty from
|
||||
http://jetty.mortbay.org/). For more information about I2PSnark, get in touch
|
||||
with the folks at http://forum.i2p.net/
|
||||
11
apps/i2psnark/readme.txt
Normal file
11
apps/i2psnark/readme.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
|
||||
|
||||
The build in tracker has been removed for simplicity.
|
||||
|
||||
Example usage:
|
||||
java -jar lib/i2psnark.jar myFile.torrent
|
||||
|
||||
or, a more verbose setting:
|
||||
java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \
|
||||
--i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \
|
||||
--debug 6 myFile.torrent
|
||||
141
apps/i2psnark/readme.txt.snark
Normal file
141
apps/i2psnark/readme.txt.snark
Normal file
@@ -0,0 +1,141 @@
|
||||
The Hunting of the Snark Project - BitTorrent Application Suite
|
||||
0.5 - The Beaver's Lesson (27 June 2003)
|
||||
|
||||
"It's a Snark!" was the sound that first came to their ears,
|
||||
And seemed almost too good to be true.
|
||||
Then followed a torrent of laughter and cheers:
|
||||
Then the ominous words "It's a Boo-"
|
||||
|
||||
-- from The Hunting Of The Snark by Lewis Carroll
|
||||
|
||||
Snark is a client for downloading and sharing files distributed with
|
||||
the BitTorrent protocol. It is mainly used for exploring the BitTorrent
|
||||
protocol and experimenting with the the GNU Compiler for Java (gcj).
|
||||
But it can also be used as a regular BitTorrent Client.
|
||||
|
||||
Snark can also act as a torrent creator, micro http server for delivering
|
||||
metainfo.torrent files and has an integrated Tracker for making sharing of
|
||||
files as easy as possible.
|
||||
|
||||
When you give the option --share Snark will automatically
|
||||
create a .torrent file, start a very simple webserver to distribute
|
||||
the metainfo.torrent file and a local tracker that other BitTorrent
|
||||
clients can connect to.
|
||||
|
||||
Distribution
|
||||
------------
|
||||
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
Snark is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Requirements/Installation
|
||||
-------------------------
|
||||
|
||||
The GNU Compiler for java (gcj) version 3.3 or later.
|
||||
(Earlier versions have a faulty SHA message digest implementation.)
|
||||
On Debian GNU/Linux based distributions just install the gcj-3.3 package.
|
||||
Edit the GCJ variable in the Makefile if your gcj binary is not gcj-3.3.
|
||||
|
||||
Typing 'make' will create the native snark binary and a snark.jar file
|
||||
for use with traditional java byte code interpreters.
|
||||
|
||||
It is possible to compile the sources with other java compilers
|
||||
like jikes or kjc to produce the snark.jar file. Edit the JAVAC and
|
||||
JAVAC_FLAGS variables on top of the Makefile for this. And type
|
||||
'make snark.jar' to create a jar file that can be used by traditional
|
||||
java bytecode interpreters like kaffe: 'kaffe -jar snark.jar'.
|
||||
You will need at least version 1.1 of kaffe for all functionality to work
|
||||
correctly ('--share' does not work with older versions).
|
||||
|
||||
When trying out the experimental Gnome frontend you also need the java-gnome
|
||||
bindings. On Debian GNU/Linux systems install the package libgnome0-java.
|
||||
You can try it out by typing 'make snark-gnome' and then run 'snark-gnome.sh'
|
||||
like you would with the normal command line client.
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To use the program start it with:
|
||||
|
||||
snark [--debug [level]] [--no-commands] [--port <port>]
|
||||
[--share (<ip>|<host>)] (<url>|<file>|<dir>)
|
||||
--debug Shows some extra info and stacktraces.
|
||||
level How much debug details to show
|
||||
(defaults to 3, with --debug to 4, highest level is 6).
|
||||
--no-commands Don't read interactive commands or show usage info.
|
||||
--port The port to listen on for incomming connections
|
||||
(if not given defaults to first free port between 6881-6889).
|
||||
--share Start torrent tracker on <ip> address or <host> name.
|
||||
<url> URL pointing to .torrent metainfo file to download/share.
|
||||
<file> Either a local .torrent metainfo file to download
|
||||
or (with --share) a file to share.
|
||||
<dir> A directory with files to share (needs --share).
|
||||
|
||||
Since this is an early beta release there are probably still some bugs
|
||||
in the program. To help find them run the program with the --debug
|
||||
option which shows more information on what it going on. You can also give
|
||||
the level of debug output you want. Zero will give (almost) no output at all.
|
||||
Everything above debug level 4 is probably to much (only really useful to
|
||||
see what goes on on the protocol/network level).
|
||||
|
||||
Examples
|
||||
|
||||
- To simple start downloading/sharing a file.
|
||||
Either download the .torrent file to disk and start snark with:
|
||||
./snark somefile.torrent
|
||||
|
||||
Or give it the complete URL:
|
||||
./snark http://somehost.example.com/cd-images/bbc-lnx.iso.torrent
|
||||
|
||||
- To start seeding/sharing a local file:
|
||||
./snark --share my-host.example.com some-file
|
||||
|
||||
Snark will respond with:
|
||||
Listening on port: 6881
|
||||
Trying to create metainfo torrent for 'some-file'
|
||||
Creating torrent piece hashes: ++++++++++
|
||||
Torrent available on http://my-host.example.com:6881/metainfo.torrent
|
||||
|
||||
You can now point other people to the above URL so they can share
|
||||
the file with their own BitTorrent client.
|
||||
|
||||
Commands
|
||||
|
||||
While the program is running in text mode you can currently give the
|
||||
following commands: 'info', 'list' and 'quit'.
|
||||
|
||||
Interactive commands are disabled when the '--no-commands' flag is given.
|
||||
This is sometimes desireable for running snark in the background.
|
||||
|
||||
More information
|
||||
----------------
|
||||
|
||||
- The Evolution of Cooperation - Robert Axelrod
|
||||
ISBN 0-465-02121-2
|
||||
|
||||
- The BitTorrent protocol description:
|
||||
<http://bitconjurer.org/BitTorrent/protocol.html>
|
||||
|
||||
- The GNU Compiler for Java (gcj):
|
||||
<http://gcc.gnu.org/java/>
|
||||
|
||||
- java-gnome bindings : <http://java-gnome.sourceforge.net/>
|
||||
|
||||
- The Hunting of the Snark - Lewis Carroll
|
||||
|
||||
Comments welcome
|
||||
|
||||
- Mark Wielaard <mark@klomp.org>
|
||||
25
apps/i2psnark/web.xml
Normal file
25
apps/i2psnark/web.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
|
||||
<web-app>
|
||||
<servlet>
|
||||
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
|
||||
<servlet-class>org.klomp.snark.web.I2PSnarkServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<!-- precompiled servlets -->
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<session-config>
|
||||
<session-timeout>
|
||||
30
|
||||
</session-timeout>
|
||||
</session-config>
|
||||
</web-app>
|
||||
@@ -37,6 +37,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
private byte _buf1[];
|
||||
protected boolean _gzip;
|
||||
private long _dataWritten;
|
||||
private InternalGZIPInputStream _in;
|
||||
private static final int CACHE_SIZE = 8*1024;
|
||||
|
||||
public HTTPResponseOutputStream(OutputStream raw) {
|
||||
@@ -65,6 +66,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if (_headerWritten) {
|
||||
out.write(buf, off, len);
|
||||
_dataWritten += len;
|
||||
//out.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,6 +82,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
// write out the remaining
|
||||
out.write(buf, off+i+1, len-i-1);
|
||||
_dataWritten += len-i-1;
|
||||
//out.flush();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -196,8 +199,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
out.flush();
|
||||
//out.flush();
|
||||
PipedInputStream pi = new PipedInputStream();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
new I2PThread(new Pusher(pi, out), "HTTP decompresser").start();
|
||||
@@ -205,22 +212,22 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
}
|
||||
|
||||
private class Pusher implements Runnable {
|
||||
private InputStream _in;
|
||||
private InputStream _inRaw;
|
||||
private OutputStream _out;
|
||||
public Pusher(InputStream in, OutputStream out) {
|
||||
_in = in;
|
||||
_inRaw = in;
|
||||
_out = out;
|
||||
}
|
||||
public void run() {
|
||||
OutputStream to = null;
|
||||
InternalGZIPInputStream in = null;
|
||||
_in = null;
|
||||
long start = System.currentTimeMillis();
|
||||
long written = 0;
|
||||
try {
|
||||
in = new InternalGZIPInputStream(_in);
|
||||
_in = new InternalGZIPInputStream(_inRaw);
|
||||
byte buf[] = new byte[8192];
|
||||
int read = -1;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read " + read + " and writing it to the browser/streams");
|
||||
_out.write(buf, 0, read);
|
||||
@@ -228,31 +235,73 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
written += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Decompressed: " + written + ", " + in.getTotalRead() + "/" + in.getTotalExpanded());
|
||||
_log.info("Decompressed: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error decompressing: " + written + ", " + in.getTotalRead() + "/" + in.getTotalExpanded(), ioe);
|
||||
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
|
||||
} finally {
|
||||
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.WARN) && (_in != null))
|
||||
_log.warn("After decompression, written=" + written +
|
||||
(_in != null ?
|
||||
" read=" + _in.getTotalRead()
|
||||
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
|
||||
+ ", finished=" + _in.getFinished()
|
||||
: ""));
|
||||
if (_out != null) try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
long compressed = in.getTotalRead();
|
||||
long expanded = in.getTotalExpanded();
|
||||
double compressed = (_in != null ? _in.getTotalRead() : 0);
|
||||
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
|
||||
double ratio = 0;
|
||||
if (expanded > 0)
|
||||
ratio = compressed/expanded;
|
||||
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", compressed, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", expanded, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
|
||||
}
|
||||
}
|
||||
private class InternalGZIPInputStream extends GZIPInputStream {
|
||||
public InternalGZIPInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
public long getTotalRead() { return super.inf.getTotalIn(); }
|
||||
public long getTotalExpanded() { return super.inf.getTotalOut(); }
|
||||
public long getTotalRead() {
|
||||
try {
|
||||
return super.inf.getTotalIn();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public long getTotalExpanded() {
|
||||
try {
|
||||
return super.inf.getTotalOut();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public long getRemaining() {
|
||||
try {
|
||||
return super.inf.getRemaining();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public boolean getFinished() {
|
||||
try {
|
||||
return super.inf.finished();
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public String toString() {
|
||||
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + ": " + _in;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
|
||||
@@ -240,6 +240,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runClient(args, l);
|
||||
} else if ("httpclient".equals(cmdname)) {
|
||||
runHttpClient(args, l);
|
||||
} else if ("ircclient".equals(cmdname)) {
|
||||
runIrcClient(args, l);
|
||||
} else if ("sockstunnel".equals(cmdname)) {
|
||||
runSOCKSTunnel(args, l);
|
||||
} else if ("config".equals(cmdname)) {
|
||||
@@ -291,6 +293,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log("gentextkeys");
|
||||
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("lookup <name>");
|
||||
l.log("quit");
|
||||
@@ -596,6 +599,60 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an IRC client on the given port number
|
||||
*
|
||||
* Sets the event "ircclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* 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 l logger to receive events and output
|
||||
*/
|
||||
public void runIrcClient(String args[], Logging l) {
|
||||
if (args.length >= 2 && args.length <= 3) {
|
||||
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("ircclientTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isShared = true;
|
||||
if (args.length > 2) {
|
||||
if ("true".equalsIgnoreCase(args[2].trim())) {
|
||||
isShared = true;
|
||||
} else if ("false".equalsIgnoreCase(args[2].trim())) {
|
||||
_log.warn("args[2] == [" + args[2] + "] and rejected explicitly");
|
||||
isShared = false;
|
||||
} else {
|
||||
// isShared not specified, default to true
|
||||
isShared = true;
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
notifyEvent("ircclientTaskId", new Integer(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ port + "]", iae);
|
||||
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
|
||||
notifyEvent("ircclientTaskId", new Integer(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("ircclient <port> [<sharedClient>]");
|
||||
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", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an SOCKS tunnel on the given port number
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
@@ -31,6 +32,7 @@ import net.i2p.util.Log;
|
||||
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelClientBase.class);
|
||||
protected I2PAppContext _context;
|
||||
protected Logging l;
|
||||
|
||||
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
@@ -101,6 +103,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.l = l;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
_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 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
|
||||
// no need to load the netDb with leaseSets for destinations that will never
|
||||
// be looked up
|
||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||
@@ -362,7 +370,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
long before = System.currentTimeMillis();
|
||||
manageConnection(s);
|
||||
long total = System.currentTimeMillis() - before;
|
||||
_context.statManager().addRateData("i2ptunnel.client.manageTime", total, total);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error listening for connections on " + localPort, ex);
|
||||
@@ -422,14 +433,20 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private Socket _s;
|
||||
public CloseEvent(Socket s) { _s = s; }
|
||||
public void timeReached() {
|
||||
int remaining = 0;
|
||||
boolean stillWaiting = false;
|
||||
synchronized (_waitingSockets) {
|
||||
stillWaiting = _waitingSockets.remove(_s);
|
||||
remaining = _waitingSockets.size();
|
||||
}
|
||||
if (stillWaiting) {
|
||||
try { _s.close(); } catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_context.statManager().addRateData("i2ptunnel.client.closeBacklog", remaining, 0);
|
||||
_log.info("Closed a waiting socket because of backlog");
|
||||
}
|
||||
} else {
|
||||
_context.statManager().addRateData("i2ptunnel.client.closeNoBacklog", remaining, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,8 +513,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
if (s != null)
|
||||
if (s != null) {
|
||||
long before = System.currentTimeMillis();
|
||||
clientConnectionRun(s);
|
||||
long total = System.currentTimeMillis() - before;
|
||||
_context.statManager().addRateData("i2ptunnel.client.buildRunTime", total, 0);
|
||||
}
|
||||
s = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class I2PTunnelGUI extends Frame implements ActionListener, Logging {
|
||||
log.setEditable(false);
|
||||
log("enter 'help' for help.");
|
||||
pack();
|
||||
show();
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
public void log(String s) {
|
||||
|
||||
@@ -96,6 +96,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_AHELPER_CONFLICT =
|
||||
("HTTP/1.1 409 Conflict\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 key conflict</H1>"+
|
||||
"The addresshelper link you followed specifies a different destination key "+
|
||||
"than a host entry in your host database. "+
|
||||
"Someone could be trying to impersonate another eepsite, "+
|
||||
"or people have given two eepsites identical names.<P/>"+
|
||||
"You can resolve the conflict by considering which key you trust, "+
|
||||
"and either discarding the addresshelper link, "+
|
||||
"discarding the host entry from your host database, "+
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@@ -179,6 +195,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
OutputStream out = null;
|
||||
@@ -243,50 +261,102 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Quick hack for foo.bar.i2p
|
||||
if (host.toLowerCase().endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
destination = host;
|
||||
// Host becomes the destination key
|
||||
host = getHostName(destination);
|
||||
if ( (host != null) && ("i2p".equals(host)) ) {
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
if (fragment.startsWith("i2paddresshelper")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
if (pos2 >= 0) {
|
||||
addressHelpers.put(destination,fragment.substring(pos2 + 1));
|
||||
}
|
||||
} else {
|
||||
// append each fragment unless it's the address helper
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String ahelperKey = null;
|
||||
boolean ahelperConflict = false;
|
||||
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
String initialFragments = fragments;
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
|
||||
// Fragment looks like addresshelper key
|
||||
if (fragment.startsWith("i2paddresshelper=")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
ahelperKey = fragment.substring(pos2 + 1);
|
||||
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey != null) {
|
||||
|
||||
// Host resolvable only with addresshelper
|
||||
if ( (host == null) || ("i2p".equals(host)) )
|
||||
{
|
||||
// Cannot check, use addresshelper key
|
||||
addressHelpers.put(destination,ahelperKey);
|
||||
} else {
|
||||
// Host resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
if (!host.equals(ahelperKey))
|
||||
{
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other fragments, just pass along
|
||||
// Append each fragment to urlEncoding
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
|
||||
// Did addresshelper key conflict?
|
||||
if (ahelperConflict)
|
||||
{
|
||||
String str;
|
||||
byte[] header;
|
||||
str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
|
||||
if (str != null) header = str.getBytes();
|
||||
else header = ERR_AHELPER_CONFLICT;
|
||||
|
||||
if (out != null) {
|
||||
long alias = I2PAppContext.getGlobalContext().random().nextLong();
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
|
||||
out.write(header);
|
||||
out.write(("To visit the destination in your host database, click <a href=\"" + trustedURL + "\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"" + conflictURL + "\">here</a>.<P/>").getBytes());
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
}
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// The request must be forwarded to a WWW proxy
|
||||
@@ -316,6 +386,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
} else {
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
if (pos < 0) {
|
||||
l.log("Invalid request url [" + request + "]");
|
||||
if (out != null) {
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
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;
|
||||
}
|
||||
destination = request.substring(0, pos);
|
||||
line = method + " " + request.substring(pos);
|
||||
}
|
||||
@@ -371,11 +453,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (line.length() == 0) {
|
||||
|
||||
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
|
||||
boolean gzip = false;
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
gzip = Boolean.valueOf(ok).booleanValue();
|
||||
if (gzip)
|
||||
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
|
||||
if (gzip) {
|
||||
// according to rfc2616 s14.3, this *should* force identity, even if
|
||||
// an explicit q=0 for gzip doesn't. tested against orion.i2p, and it
|
||||
// seems to work.
|
||||
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");
|
||||
}
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
@@ -415,27 +502,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
|
||||
String str;
|
||||
byte[] header;
|
||||
boolean showAddrHelper = false;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else if(ahelper != 0)
|
||||
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
else {
|
||||
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
|
||||
showAddrHelper = true;
|
||||
}
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination, showAddrHelper);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
String remoteID;
|
||||
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
|
||||
//opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
|
||||
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
|
||||
// dont want to hard link to here
|
||||
opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
|
||||
//opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
|
||||
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
@@ -494,7 +584,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
@@ -506,6 +596,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
if (showAddrHelper) {
|
||||
out.write("<br><br>Click below to try an address helper link:<br><br><a href=\"http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
}
|
||||
}
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
@@ -531,7 +628,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
@@ -30,12 +27,38 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
private Log _log;
|
||||
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
|
||||
}
|
||||
|
||||
protected OutputStream getSocketOut() throws IOException {
|
||||
OutputStream raw = super.getSocketOut();
|
||||
return new HTTPResponseOutputStream(raw);
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
i2pin.close();
|
||||
i2pout.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the i2p socket output stream: " + i2pout, ioe);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the browser output stream: " + out, ioe);
|
||||
}
|
||||
i2ps.close();
|
||||
s.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.net.SocketException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -71,6 +72,14 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
// we keep the enc sent by the browser before clobbering it, since it may have
|
||||
// been x-i2p-gzip
|
||||
String enc = headers.getProperty("Accept-encoding");
|
||||
String altEnc = headers.getProperty("X-Accept-encoding");
|
||||
|
||||
// according to rfc2616 s14.3, this *should* force identity, even if
|
||||
// "identity;q=1, *;q=0" didn't.
|
||||
headers.setProperty("Accept-encoding", "");
|
||||
String modifiedHeader = formatHeaders(headers, command);
|
||||
|
||||
//String modifiedHeader = getModifiedHeader(socket);
|
||||
@@ -91,10 +100,13 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
|
||||
allowGZIP = false;
|
||||
}
|
||||
String enc = headers.getProperty("Accept-encoding");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HTTP server encoding header: " + enc);
|
||||
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
|
||||
_log.info("HTTP server encoding header: " + enc + "/" + altEnc);
|
||||
boolean useGZIP = ( (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) );
|
||||
if ( (!useGZIP) && (altEnc != null) && (altEnc.indexOf("x-i2p-gzip") >= 0) )
|
||||
useGZIP = true;
|
||||
|
||||
if (allowGZIP && useGZIP) {
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
|
||||
req.start();
|
||||
} else {
|
||||
@@ -178,7 +190,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Begin sending");
|
||||
try {
|
||||
byte buf[] = new byte[4096];
|
||||
byte buf[] = new byte[16*1024];
|
||||
int read = 0;
|
||||
int total = 0;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
@@ -189,13 +201,13 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Done sending: " + total);
|
||||
_out.flush();
|
||||
//_out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error sending", ioe);
|
||||
} finally {
|
||||
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
|
||||
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
|
||||
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,19 +228,45 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
protected void beginProcessing() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Beginning compression processing");
|
||||
out.flush();
|
||||
//out.flush();
|
||||
_gzipOut = new InternalGZIPOutputStream(out);
|
||||
out = _gzipOut;
|
||||
}
|
||||
public long getTotalRead() { return _gzipOut.getTotalRead(); }
|
||||
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
|
||||
public long getTotalRead() {
|
||||
InternalGZIPOutputStream gzipOut = _gzipOut;
|
||||
if (gzipOut != null)
|
||||
return gzipOut.getTotalRead();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
public long getTotalCompressed() {
|
||||
InternalGZIPOutputStream gzipOut = _gzipOut;
|
||||
if (gzipOut != null)
|
||||
return gzipOut.getTotalCompressed();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
private class InternalGZIPOutputStream extends GZIPOutputStream {
|
||||
public InternalGZIPOutputStream(OutputStream target) throws IOException {
|
||||
super(target);
|
||||
}
|
||||
public long getTotalRead() { return super.def.getTotalIn(); }
|
||||
public long getTotalCompressed() { return super.def.getTotalOut(); }
|
||||
public long getTotalRead() {
|
||||
try {
|
||||
return def.getTotalIn();
|
||||
} catch (Exception e) {
|
||||
// j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalIn() implementation
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public long getTotalCompressed() {
|
||||
try {
|
||||
return def.getTotalOut();
|
||||
} catch (Exception e) {
|
||||
// j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalOut() implementation
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
@@ -267,6 +305,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
String value = buf.substring(split+2); // ": "
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
else if ("X-Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "X-Accept-encoding";
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelIRCClient.class);
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
/** list of Destination objects that we point at */
|
||||
protected List dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
public I2PTunnelIRCClient(
|
||||
int localPort,
|
||||
String destinations,
|
||||
Logging l,
|
||||
boolean ownDest,
|
||||
EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort,
|
||||
ownDest,
|
||||
l,
|
||||
notifyThis,
|
||||
"IRCHandler " + (++__clientId), tunnel);
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ",");
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String destination = tok.nextToken();
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(dest);
|
||||
} catch (DataFormatException dfe) {
|
||||
l.log("Bad format parsing \"" + destination + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
l.log("No target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> IRCClient");
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openIRCClientResult", "ok");
|
||||
}
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("got a connection.");
|
||||
Destination dest = pickDestination();
|
||||
I2PSocket i2ps = null;
|
||||
try {
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
|
||||
in.start();
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
mySockets.remove(sockLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Destination pickDestination() {
|
||||
int size = dests.size();
|
||||
if (size <= 0) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("No client targets?!");
|
||||
return null;
|
||||
}
|
||||
if (size == 1) // skip the rand in the most common case
|
||||
return (Destination)dests.get(0);
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return (Destination)dests.get(index);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
*/
|
||||
private class IrcInboundFilter implements Runnable {
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
InputStream input;
|
||||
OutputStream output;
|
||||
try {
|
||||
input=remote.getInputStream();
|
||||
output=local.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IrcInboundFilter: no streams",e);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcInboundFilter: Running.");
|
||||
try {
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = inboundFilter(inmsg);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("inbound FILTERED: "+outmsg);
|
||||
_log.warn(" - inbound was: "+inmsg);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("inbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("inbound BLOCKED: "+inmsg);
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IrcInboundFilter: disconnected",e1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
_log.error("Error filtering inbound data", re);
|
||||
} finally {
|
||||
if (local != null) try { local.close(); } catch (IOException e) {}
|
||||
}
|
||||
if(_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcInboundFilter: Done.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
*/
|
||||
private class IrcOutboundFilter implements Runnable {
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
InputStream input;
|
||||
OutputStream output;
|
||||
try {
|
||||
input=local.getInputStream();
|
||||
output=remote.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IrcOutboundFilter: no streams",e);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcOutboundFilter: Running.");
|
||||
try {
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = outboundFilter(inmsg);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("outbound FILTERED: "+outmsg);
|
||||
_log.warn(" - outbound was: "+inmsg);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("outbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IrcOutboundFilter: disconnected",e1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
_log.error("Error filtering outbound data", re);
|
||||
} finally {
|
||||
if (remote != null) try { remote.close(); } catch (IOException e) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcOutboundFilter: Done.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
*/
|
||||
|
||||
public static String inboundFilter(String s) {
|
||||
|
||||
String field[]=s.split(" ",4);
|
||||
String command;
|
||||
int idx=0;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
"PING",
|
||||
"PONG",
|
||||
"MODE",
|
||||
"JOIN",
|
||||
"NICK",
|
||||
"QUIT",
|
||||
"PART",
|
||||
"WALLOPS",
|
||||
"ERROR",
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
if(field[0].charAt(0)==':')
|
||||
idx++;
|
||||
|
||||
command = field[idx++];
|
||||
|
||||
idx++; //skip victim
|
||||
|
||||
// Allow numerical responses
|
||||
try {
|
||||
new Integer(command);
|
||||
return s;
|
||||
} catch(NumberFormatException nfe){}
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++) {
|
||||
if(allowedCommands[i].equals(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP.
|
||||
if("PRIVMSG".equals(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[idx++];
|
||||
|
||||
byte[] bytes = msg.getBytes();
|
||||
if(bytes[1]==0x01)
|
||||
{
|
||||
// CTCP
|
||||
msg=msg.substring(2);
|
||||
if(msg.startsWith("ACTION ")) {
|
||||
// /me says hello
|
||||
return s;
|
||||
}
|
||||
return null; // Block all other ctcp
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Block the rest
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String outboundFilter(String s) {
|
||||
|
||||
String field[]=s.split(" ",3);
|
||||
String command;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
"PONG",
|
||||
"MODE",
|
||||
"JOIN",
|
||||
"NICK",
|
||||
"WHO",
|
||||
"WHOIS",
|
||||
"LIST",
|
||||
"NAMES",
|
||||
"NICK",
|
||||
// "QUIT", // replace with a filtered QUIT to hide client quit messages
|
||||
"SILENCE",
|
||||
"MAP", // seems safe enough, the ircd should protect themselves though
|
||||
"PART",
|
||||
"OPER",
|
||||
"PING",
|
||||
"KICK",
|
||||
"HELPME",
|
||||
"RULES",
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
if(field[0].length()==0)
|
||||
return null; // W T F?
|
||||
|
||||
|
||||
if(field[0].charAt(0)==':')
|
||||
return null; // wtf
|
||||
|
||||
command = field[0].toUpperCase();
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++)
|
||||
{
|
||||
if(allowedCommands[i].equals(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP (except ACTION).
|
||||
if("PRIVMSG".equals(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[2];
|
||||
|
||||
byte[] bytes = msg.getBytes();
|
||||
if(bytes[1]==0x01)
|
||||
{
|
||||
// CTCP
|
||||
msg=msg.substring(2);
|
||||
if(msg.startsWith("ACTION ")) {
|
||||
// /me says hello
|
||||
return s;
|
||||
}
|
||||
return null; // Block all other ctcp
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
if("USER".equals(command)) {
|
||||
int idx = field[2].lastIndexOf(":");
|
||||
if(idx<0)
|
||||
return "USER user hostname localhost :realname";
|
||||
String realname = field[2].substring(idx+1);
|
||||
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
||||
return ret;
|
||||
} else if ("QUIT".equals(command)) {
|
||||
return "QUIT :leaving";
|
||||
}
|
||||
|
||||
// Block the rest
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -153,11 +153,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
onTimeout.run();
|
||||
}
|
||||
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// now one connection is dead - kill the other as well, after making sure we flush
|
||||
close(out, in, i2pout, i2pin, s, i2ps, t1, t2);
|
||||
} catch (InterruptedException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
@@ -188,6 +185,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
i2pout.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
in.close();
|
||||
i2pin.close();
|
||||
// ok, yeah, there's a race here in theory, if data comes in after flushing and before
|
||||
// closing, but its better than before...
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
public void errorOccurred() {
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
|
||||
@@ -135,6 +135,8 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
if ("httpclient".equals(type)) {
|
||||
startHttpClient();
|
||||
}else if("ircclient".equals(type)) {
|
||||
startIrcClient();
|
||||
} else if ("client".equals(type)) {
|
||||
startClient();
|
||||
} else if ("server".equals(type)) {
|
||||
@@ -162,6 +164,18 @@ public class TunnelController implements Logging {
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startIrcClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String dest = getTargetDestination();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note the fact that we are using some sessions, so that they dont get
|
||||
* closed by some other tunnels
|
||||
|
||||
@@ -30,7 +30,9 @@ 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())) );
|
||||
return ( ("client".equals(cur.getType())) ||
|
||||
("httpclient".equals(cur.getType()))||
|
||||
("ircclient".equals(cur.getType())));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -137,23 +139,63 @@ public class EditBean extends IndexBean {
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelCount(int tunnel, int defaultCount) {
|
||||
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 defaultCount;
|
||||
if (len == null) return defaultQuantity;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultCount;
|
||||
return defaultQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
return defaultQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelVariance(int tunnel, int defaultVariance) {
|
||||
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;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultVariance;
|
||||
}
|
||||
} else {
|
||||
return defaultVariance;
|
||||
}
|
||||
} else {
|
||||
return defaultVariance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +227,10 @@ public class EditBean extends IndexBean {
|
||||
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;
|
||||
@@ -222,4 +268,4 @@ public class EditBean extends IndexBean {
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,9 @@ public class IndexBean {
|
||||
private String _i2cpHost;
|
||||
private String _i2cpPort;
|
||||
private String _tunnelDepth;
|
||||
private String _tunnelCount;
|
||||
private String _tunnelQuantity;
|
||||
private String _tunnelVariance;
|
||||
private String _tunnelBackupQuantity;
|
||||
private boolean _connectDelay;
|
||||
private String _customOptions;
|
||||
private String _proxyList;
|
||||
@@ -64,6 +66,10 @@ public class IndexBean {
|
||||
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
|
||||
static final String CLIENT_NICKNAME = "shared clients";
|
||||
|
||||
public static final String PROP_THEME_NAME = "routerconsole.theme";
|
||||
public static final String PROP_CSS_DISABLED = "routerconsole.css.disabled";
|
||||
public static final String PROP_JS_DISABLED = "routerconsole.javascript.disabled";
|
||||
|
||||
public IndexBean() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(IndexBean.class);
|
||||
@@ -121,21 +127,23 @@ public class IndexBean {
|
||||
return "";
|
||||
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
|
||||
return "Invalid nonce, are you being spoofed?";
|
||||
if ("Stop all tunnels".equals(_action))
|
||||
if ("Stop all".equals(_action))
|
||||
return stopAll();
|
||||
else if ("Start all tunnels".equals(_action))
|
||||
else if ("Start all".equals(_action))
|
||||
return startAll();
|
||||
else if ("Restart all".equals(_action))
|
||||
return restartAll();
|
||||
else if ("Reload config".equals(_action))
|
||||
else if ("Reload configuration".equals(_action))
|
||||
return reloadConfig();
|
||||
else if ("stop".equals(_action))
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else if ("Save changes".equals(_action))
|
||||
else if ("Save changes".equals(_action) || // IE workaround:
|
||||
(_action.toLowerCase().indexOf("s</span>ave") >= 0))
|
||||
return saveChanges();
|
||||
else if ("Delete this proxy".equals(_action))
|
||||
else if ("Delete this proxy".equals(_action) || // IE workaround:
|
||||
(_action.toLowerCase().indexOf("d</span>elete") >= 0))
|
||||
return deleteTunnel();
|
||||
else
|
||||
return "Action " + _action + " unknown";
|
||||
@@ -198,7 +206,9 @@ public class IndexBean {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
if ("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
@@ -206,16 +216,27 @@ public class IndexBean {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
//only change when they really are declared of beeing a sharedClient
|
||||
if (("httpclient".equals(c.getType()) || "client".equals(c.getType())) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
if (("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType())||
|
||||
"client".equals(c.getType())
|
||||
) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
if (_tunnelQuantity != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelQuantity);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
cOpt.setProperty("option.inbound.length", _tunnelDepth);
|
||||
cOpt.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
if (_tunnelVariance != null) {
|
||||
cOpt.setProperty("option.inbound.lengthVariance", _tunnelVariance);
|
||||
cOpt.setProperty("option.outbound.lengthVariance", _tunnelVariance);
|
||||
}
|
||||
if (_tunnelBackupQuantity != null) {
|
||||
cOpt.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
|
||||
cOpt.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
|
||||
}
|
||||
cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
|
||||
@@ -270,6 +291,24 @@ public class IndexBean {
|
||||
// The remaining methods are simple bean props for the jsp to query
|
||||
////
|
||||
|
||||
public String getTheme() {
|
||||
String theme = _context.getProperty(PROP_THEME_NAME);
|
||||
if (theme != null)
|
||||
return "/themes/console/" + theme + "/";
|
||||
else
|
||||
return "/themes/console/";
|
||||
}
|
||||
|
||||
public boolean allowCSS() {
|
||||
String css = _context.getProperty(PROP_CSS_DISABLED);
|
||||
return (css == null);
|
||||
}
|
||||
|
||||
public boolean allowJS() {
|
||||
String js = _context.getProperty(PROP_JS_DISABLED);
|
||||
return (js == null);
|
||||
}
|
||||
|
||||
public int getTunnelCount() {
|
||||
if (_group == null) return 0;
|
||||
return _group.getControllers().size();
|
||||
@@ -278,7 +317,9 @@ 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())) );
|
||||
return ( ("client".equals(cur.getType())) ||
|
||||
("httpclient".equals(cur.getType())) ||
|
||||
("ircclient".equals(cur.getType())));
|
||||
}
|
||||
|
||||
public String getTunnelName(int tunnel) {
|
||||
@@ -306,9 +347,10 @@ public class IndexBean {
|
||||
}
|
||||
|
||||
public String getTypeName(String internalType) {
|
||||
if ("client".equals(internalType)) return "Client proxy";
|
||||
else if ("httpclient".equals(internalType)) return "HTTP proxy";
|
||||
else if ("server".equals(internalType)) return "Server";
|
||||
if ("client".equals(internalType)) return "Standard client";
|
||||
else if ("httpclient".equals(internalType)) return "HTTP client";
|
||||
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 return internalType;
|
||||
}
|
||||
@@ -356,7 +398,7 @@ public class IndexBean {
|
||||
public String getClientDestination(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return "";
|
||||
if ("client".equals(tun.getType())) return tun.getTargetDestination();
|
||||
if ("client".equals(tun.getType())||"ircclient".equals(tun.getType())) return tun.getTargetDestination();
|
||||
else return tun.getProxyList();
|
||||
}
|
||||
|
||||
@@ -386,7 +428,7 @@ public class IndexBean {
|
||||
///
|
||||
|
||||
/**
|
||||
* What type of tunnel (httpclient, client, or server). This is
|
||||
* What type of tunnel (httpclient, ircclient, client, or server). This is
|
||||
* required when adding a new tunnel.
|
||||
*
|
||||
*/
|
||||
@@ -416,8 +458,16 @@ public class IndexBean {
|
||||
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
|
||||
}
|
||||
/** how many parallel inbound tunnels to use */
|
||||
public void setTunnelCount(String tunnelCount) {
|
||||
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
|
||||
public void setTunnelQuantity(String tunnelQuantity) {
|
||||
_tunnelQuantity = (tunnelQuantity != null ? tunnelQuantity.trim() : null);
|
||||
}
|
||||
/** how much randomisation to apply to the depth of tunnels */
|
||||
public void setTunnelVariance(String tunnelVariance) {
|
||||
_tunnelVariance = (tunnelVariance != null ? tunnelVariance.trim() : null);
|
||||
}
|
||||
/** how many tunnels to hold in reserve to guard against failures */
|
||||
public void setTunnelBackupQuantity(String tunnelBackupQuantity) {
|
||||
_tunnelBackupQuantity = (tunnelBackupQuantity != null ? tunnelBackupQuantity.trim() : null);
|
||||
}
|
||||
/** what I2P session overrides should be used */
|
||||
public void setCustomOptions(String customOptions) {
|
||||
@@ -427,12 +477,12 @@ public class IndexBean {
|
||||
public void setProxyList(String proxyList) {
|
||||
_proxyList = (proxyList != null ? proxyList.trim() : null);
|
||||
}
|
||||
/** what port should this client/httpclient listen on */
|
||||
/** what port should this client/httpclient/ircclient listen on */
|
||||
public void setPort(String port) {
|
||||
_port = (port != null ? port.trim() : null);
|
||||
}
|
||||
/**
|
||||
* what interface should this client/httpclient listen on (unless
|
||||
* what interface should this client/httpclient/ircclient listen on (unless
|
||||
* overridden by the setReachableByOther() field)
|
||||
*/
|
||||
public void setReachableBy(String reachableBy) {
|
||||
@@ -440,7 +490,7 @@ public class IndexBean {
|
||||
}
|
||||
/**
|
||||
* If specified, defines the exact IP interface to listen for requests
|
||||
* on (in the case of client/httpclient tunnels)
|
||||
* on (in the case of client/httpclient/ircclient tunnels)
|
||||
*/
|
||||
public void setReachableByOther(String reachableByOther) {
|
||||
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
|
||||
@@ -520,6 +570,24 @@ public class IndexBean {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
@@ -556,6 +624,7 @@ public class IndexBean {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -585,6 +654,10 @@ public class IndexBean {
|
||||
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;
|
||||
@@ -595,20 +668,28 @@ public class IndexBean {
|
||||
|
||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
||||
|
||||
if (_tunnelCount != null) {
|
||||
config.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
config.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
if (_tunnelQuantity != null) {
|
||||
config.setProperty("option.inbound.quantity", _tunnelQuantity);
|
||||
config.setProperty("option.outbound.quantity", _tunnelQuantity);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
config.setProperty("option.inbound.length", _tunnelDepth);
|
||||
config.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
if (_tunnelVariance != null) {
|
||||
config.setProperty("option.inbound.lengthVariance", _tunnelVariance);
|
||||
config.setProperty("option.outbound.lengthVariance", _tunnelVariance);
|
||||
}
|
||||
if (_tunnelBackupQuantity != null) {
|
||||
config.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
|
||||
config.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
|
||||
}
|
||||
if (_connectDelay)
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))) || (!_sharedClient) ) {
|
||||
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))&& (!"ircclient".equals(_type))) || (!_sharedClient) ) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
} else {
|
||||
@@ -647,4 +728,4 @@ public class IndexBean {
|
||||
buf.append((String)msgs.get(i)).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
if (tun != null) {
|
||||
try {
|
||||
int curTunnel = Integer.parseInt(tun);
|
||||
if (EditBean.staticIsClient(curTunnel)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
%>Invalid tunnel parameter<%
|
||||
}
|
||||
} else {
|
||||
String type = request.getParameter("type");
|
||||
int curTunnel = -1;
|
||||
if ("client".equals(type) || "httpclient".equals(type)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else if ("server".equals(type) || "httpserver".equals(type)) {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %><%
|
||||
String tun = request.getParameter("tunnel");
|
||||
if (tun != null) {
|
||||
try {
|
||||
int curTunnel = Integer.parseInt(tun);
|
||||
if (EditBean.staticIsClient(curTunnel)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else {
|
||||
%>Invalid tunnel type<%
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
%>Invalid tunnel parameter<%
|
||||
}
|
||||
} else {
|
||||
String type = request.getParameter("type");
|
||||
int curTunnel = -1;
|
||||
if ("client".equals(type) || "httpclient".equals(type) || "ircclient".equals(type)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else if ("server".equals(type) || "httpserver".equals(type)) {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
} else {
|
||||
%>Invalid tunnel type<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
@@ -1,4 +1,5 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean"%><?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
@@ -10,284 +11,277 @@
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
<title>I2PTunnel Webmanager - Edit</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
|
||||
|
||||
<% if (editBean.allowCSS()) {
|
||||
%><link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
|
||||
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
|
||||
<% }
|
||||
%>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit proxy settings</b>
|
||||
<% } else { %>
|
||||
<b>New proxy settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Listening Port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="6" maxlength="5" name="port" value="<%=editBean.getClientPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b> Accessable by:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="reachableBy">
|
||||
<% String clientInterface = editBean.getClientInterface(curTunnel); %>
|
||||
<% if (("127.0.0.1".equals(clientInterface)) || (clientInterface == null) || (clientInterface.trim().length() <= 0)) { %>
|
||||
<option value="127.0.0.1" selected="true">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
|
||||
<option value="other">LAN Hosts (Please specify your LAN address)</option>
|
||||
<body id="tunnelEditPage">
|
||||
<div id="pageHeader">
|
||||
</div>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" />
|
||||
<% } else if ("0.0.0.0".equals(clientInterface)) { %>
|
||||
<option value="127.0.0.1">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
|
||||
<option value="other">LAN Hosts (Please specify your LAN address)</option>
|
||||
<form method="post" action="index.jsp">
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" />
|
||||
<% } else { %>
|
||||
<option value="127.0.0.1">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
|
||||
<option value="other" selected="true">LAN Hosts (Please specify your LAN address)</option>
|
||||
<div id="tunnelEditPanel" class="panel">
|
||||
<div class="header">
|
||||
<%
|
||||
String tunnelTypeName = "";
|
||||
String tunnelType = "";
|
||||
if (curTunnel >= 0) {
|
||||
tunnelTypeName = editBean.getTunnelType(curTunnel);
|
||||
tunnelType = editBean.getInternalType(curTunnel);
|
||||
%><h4>Edit proxy settings</h4><%
|
||||
} else {
|
||||
tunnelTypeName = editBean.getTypeName(request.getParameter("type"));
|
||||
tunnelType = request.getParameter("type");
|
||||
%><h4>New proxy settings</h4><%
|
||||
} %>
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<input type="hidden" name="type" value="<%=tunnelType%>" />
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" value="<%=clientInterface%>" />
|
||||
<% } %>
|
||||
<div id="nameField" class="rowItem">
|
||||
<label for="name" accesskey="N">
|
||||
<span class="accessKey">N</span>ame:
|
||||
</label>
|
||||
<input type="text" size="30" maxlength="50" name="name" id="name" title="Tunnel Name" value="<%=editBean.getTunnelName(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="typeField" class="rowItem">
|
||||
<label>Type:</label>
|
||||
<span class="text"><%=tunnelTypeName%></span>
|
||||
</div>
|
||||
<div id="descriptionField" class="rowItem">
|
||||
<label for="description" accesskey="e">
|
||||
D<span class="accessKey">e</span>scription:
|
||||
</label>
|
||||
<input type="text" size="60" maxlength="80" name="description" id="description" title="Tunnel Description" value="<%=editBean.getTunnelDescription(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="accessField" class="rowItem">
|
||||
<label>Access Point:</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="port" accesskey="P">
|
||||
<span class="accessKey">P</span>ort:
|
||||
</label>
|
||||
<input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<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)) &&
|
||||
!("0.0.0.0".equals(clientInterface)) &&
|
||||
(clientInterface != null) &&
|
||||
(clientInterface.trim().length() > 0)) {
|
||||
otherInterface = clientInterface;
|
||||
}
|
||||
%><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>>Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>>Everyone (0.0.0.0)</option>
|
||||
<option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>>LAN Hosts (Please specify your LAN address)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="otherField" class="rowItem">
|
||||
<label for="reachableByOther" accesskey="O">
|
||||
<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>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) {
|
||||
%><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 {
|
||||
%><div id="destinationField" class="rowItem">
|
||||
<label for="targetDestination" accesskey="T">
|
||||
<span class="accessKey">T</span>unnel Destination:
|
||||
</label>
|
||||
<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">
|
||||
<label for="profile" accesskey="f">
|
||||
Pro<span class="accessKey">f</span>ile:
|
||||
</label>
|
||||
<select id="profile" name="profile" title="Connection Profile" class="selectbox">
|
||||
<% boolean interactiveProfile = editBean.isInteractive(curTunnel);
|
||||
%><option <%=(interactiveProfile == true ? "selected=\"selected\" " : "")%>value="interactive">interactive connection </option>
|
||||
<option <%=(interactiveProfile == false ? "selected=\"selected\" " : "")%>value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="delayConnectField" class="rowItem">
|
||||
<label for="connectDelay" accesskey="y">
|
||||
Dela<span class="accessKey">y</span> Connect:
|
||||
</label>
|
||||
<input value="1000" type="checkbox" id="connectDelay" name="connectDelay" title="Delay Connection"<%=(editBean.shouldDelay(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
<span class="comment">(for request/response connections)</span>
|
||||
</div>
|
||||
<div id="sharedtField" class="rowItem">
|
||||
<label for="shared" accesskey="h">
|
||||
S<span class="accessKey">h</span>ared Client:
|
||||
</label>
|
||||
<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>
|
||||
<div id="startupField" class="rowItem">
|
||||
<label for="startOnLoad" accesskey="a">
|
||||
<span class="accessKey">A</span>uto Start:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="startOnLoad" name="startOnLoad" title="Start Tunnel Automatically"<%=(editBean.startAutomatically(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
<span class="comment">(Check the Box for 'YES')</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
|
||||
<td><b>Outproxies:</b>
|
||||
<% } else { %>
|
||||
<td><b>Target:</b>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
|
||||
<input type="text" name="proxyList" value="<%=editBean.getClientDestination(curTunnel)%>" />
|
||||
<% } else { %>
|
||||
<input type="text" name="targetDestination" value="<%=editBean.getClientDestination(curTunnel)%>" />
|
||||
<% } %>
|
||||
<i>(name or destination)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Delayed connect?</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.shouldDelay(curTunnel)) { %>
|
||||
<input type="checkbox" value="1000" name="connectDelay" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="1000" name="connectDelay" />
|
||||
<% } %>
|
||||
<i>(for request/response connections)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Shared Client</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.isSharedClient(curTunnel)) { %>
|
||||
<input type="checkbox" value="true" name="shared" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="true" name="shared" />
|
||||
<% } %>
|
||||
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
<span style="color:#dd0000;">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span></b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<b><span style="color:#dd0000;">confirm delete:</span></b>
|
||||
<input type="checkbox" value="true" name="removeConfirm" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
<div id="tunnelAdvancedNetworking" class="panel">
|
||||
<div class="header">
|
||||
<h4>Advanced networking options</h4>
|
||||
<span class="comment">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span>
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="tunnelOptionsField" class="rowItem">
|
||||
<label>Tunnel Options:</label>
|
||||
</div>
|
||||
<div id="depthField" class="rowItem">
|
||||
<label for="tunnelDepth" accesskey="t">
|
||||
Dep<span class="accessKey">t</span>h:
|
||||
</label>
|
||||
<select id="tunnelDepth" name="tunnelDepth" title="Depth of each Tunnel" class="selectbox">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% if (tunnelDepth > 2) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
<div id="varianceField" class="rowItem">
|
||||
<label for="tunnelVariance" accesskey="v">
|
||||
<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);
|
||||
%><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>
|
||||
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
|
||||
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
<div id="countField" class="rowItem">
|
||||
<label for="tunnelQuantity" accesskey="C">
|
||||
<span class="accessKey">C</span>ount:
|
||||
</label>
|
||||
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
|
||||
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% if (tunnelQuantity > 3) {
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
<div id="backupField" class="rowItem">
|
||||
<label for="tunnelBackupQuantity" accesskey="b">
|
||||
<span class="accessKey">B</span>ackup Count:
|
||||
</label>
|
||||
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
|
||||
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
|
||||
<% if (tunnelBackupQuantity > 3) {
|
||||
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label>I2CP Options:</label>
|
||||
</div>
|
||||
<div id="optionsHostField" class="rowItem">
|
||||
<label for="clientHost" accesskey="o">
|
||||
H<span class="accessKey">o</span>st:
|
||||
</label>
|
||||
<input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="optionsPortField" class="rowItem">
|
||||
<label for="clientPort" accesskey="r">
|
||||
Po<span class="accessKey">r</span>t:
|
||||
</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="customOptionsField" class="rowItem">
|
||||
<label for="customOptions" accesskey="u">
|
||||
C<span class="accessKey">u</span>stom options:
|
||||
</label>
|
||||
<input type="text" id="customOptions" name="customOptions" size="60" title="Custom Options" value="<%=editBean.getCustomOptions(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
</div>
|
||||
</div>
|
||||
<div id="globalOperationsPanel" class="panel">
|
||||
<div class="header"></div>
|
||||
<div class="footer">
|
||||
<div class="toolbox">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,5 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean"%><?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
@@ -10,224 +11,249 @@
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
<title>I2PTunnel Webmanager - Edit</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
|
||||
|
||||
<% if (editBean.allowCSS()) {
|
||||
%><link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
|
||||
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
|
||||
<% }
|
||||
%>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit server settings</b>
|
||||
<% } else { %>
|
||||
<b>New server settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Target:</b>
|
||||
</td>
|
||||
<td>
|
||||
Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
|
||||
Port: <input type="text" size="6" maxlength="5" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<% String curType = editBean.getInternalType(curTunnel);
|
||||
if ( (curType == null) || (curType.trim().length() <= 0) )
|
||||
curType = request.getParameter("type");
|
||||
if ("httpserver".equals(curType)) { %>
|
||||
<tr>
|
||||
<td><b>Website name:</b></td>
|
||||
<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
|
||||
</td></tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
<td><b>Private key file:</b>
|
||||
</td>
|
||||
<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" align="left"><b>Local destination:</b><br /><i>(if known)</i></td>
|
||||
<td valign="top" align="left"><input type="text" size="60" value="<%=editBean.getDestinationBase64(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<b><span style="color:#dd0000;">confirm delete:</span></b>
|
||||
<input type="checkbox" value="true" name="removeConfirm" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<body id="tunnelEditPage">
|
||||
<div id="pageHeader">
|
||||
</div>
|
||||
|
||||
<form method="post" action="index.jsp">
|
||||
|
||||
<div id="tunnelEditPanel" class="panel">
|
||||
<div class="header">
|
||||
<%
|
||||
String tunnelTypeName = "";
|
||||
String tunnelType = "";
|
||||
if (curTunnel >= 0) {
|
||||
tunnelTypeName = editBean.getTunnelType(curTunnel);
|
||||
tunnelType = editBean.getInternalType(curTunnel);
|
||||
%><h4>Edit server settings</h4><%
|
||||
} else {
|
||||
tunnelTypeName = editBean.getTypeName(request.getParameter("type"));
|
||||
tunnelType = request.getParameter("type");
|
||||
%><h4>New server settings</h4><%
|
||||
} %>
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<input type="hidden" name="type" value="<%=tunnelType%>" />
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="nameField" class="rowItem">
|
||||
<label for="name" accesskey="N">
|
||||
<span class="accessKey">N</span>ame:
|
||||
</label>
|
||||
<input type="text" size="30" maxlength="50" name="name" id="name" title="Tunnel Name" value="<%=editBean.getTunnelName(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="typeField" class="rowItem">
|
||||
<label>Type:</label>
|
||||
<span class="text"><%=tunnelTypeName%></span>
|
||||
</div>
|
||||
<div id="descriptionField" class="rowItem">
|
||||
<label for="description" accesskey="e">
|
||||
D<span class="accessKey">e</span>scription:
|
||||
</label>
|
||||
<input type="text" size="60" maxlength="80" name="description" id="description" title="Tunnel Description" value="<%=editBean.getTunnelDescription(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="startupField" class="rowItem">
|
||||
<label for="startOnLoad" accesskey="a">
|
||||
<span class="accessKey">A</span>uto Start:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="startOnLoad" name="startOnLoad" title="Start Tunnel Automatically"<%=(editBean.startAutomatically(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
<span class="comment">(Check the Box for 'YES')</span>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="targetField" class="rowItem">
|
||||
<label>Target:</label>
|
||||
</div>
|
||||
<div id="hostField" class="rowItem">
|
||||
<label for="targetHost" accesskey="H">
|
||||
<span class="accessKey">H</span>ost:
|
||||
</label>
|
||||
<input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=editBean.getTargetHost(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label for="targetPort" accesskey="P">
|
||||
<span class="accessKey">P</span>ort:
|
||||
</label>
|
||||
<input type="text" size="6" maxlength="5" id="targetPort" name="targetPort" title="Target Port Number" value="<%=editBean.getTargetPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<% if ("httpserver".equals(tunnelType)) {
|
||||
%><div id="websiteField" class="rowItem">
|
||||
<label for="spoofedHost" accesskey="W">
|
||||
<span class="accessKey">W</span>ebsite name:
|
||||
</label>
|
||||
<input type="text" size="20" id="spoofedHost" name="spoofedHost" title="Website Host Name" value="<%=editBean.getSpoofedHost(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<% }
|
||||
%><div id="privKeyField" class="rowItem">
|
||||
<label for="privKeyFile" accesskey="k">
|
||||
Private <span class="accessKey">k</span>ey file:
|
||||
</label>
|
||||
<input type="text" size="30" id="privKeyFile" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="profileField" class="rowItem">
|
||||
<label for="profile" accesskey="f">
|
||||
Pro<span class="accessKey">f</span>ile:
|
||||
</label>
|
||||
<select id="profile" name="profile" title="Connection Profile" class="selectbox">
|
||||
<% boolean interactiveProfile = editBean.isInteractive(curTunnel);
|
||||
%><option <%=(interactiveProfile == true ? "selected=\"selected\" " : "")%>value="interactive">interactive connection </option>
|
||||
<option <%=(interactiveProfile == false ? "selected=\"selected\" " : "")%>value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="destinationField" class="rowItem">
|
||||
<label for="localDestination" accesskey="L">
|
||||
<span class="accessKey">L</span>ocal destination:
|
||||
</label>
|
||||
<input type="text" size="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" value="<%=editBean.getDestinationBase64(curTunnel)%>" class="freetext" />
|
||||
<span class="comment">(if known)</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tunnelAdvancedNetworking" class="panel">
|
||||
<div class="header">
|
||||
<h4>Advanced networking options</h4>
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="tunnelOptionsField" class="rowItem">
|
||||
<label>Tunnel Options:</label>
|
||||
</div>
|
||||
<div id="depthField" class="rowItem">
|
||||
<label for="tunnelDepth" accesskey="t">
|
||||
Dep<span class="accessKey">t</span>h:
|
||||
</label>
|
||||
<select id="tunnelDepth" name="tunnelDepth" title="Depth of each Tunnel" class="selectbox">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% if (tunnelDepth > 2) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
<div id="varianceField" class="rowItem">
|
||||
<label for="tunnelVariance" accesskey="v">
|
||||
<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);
|
||||
%><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>
|
||||
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
|
||||
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
<div id="countField" class="rowItem">
|
||||
<label for="tunnelQuantity" accesskey="C">
|
||||
<span class="accessKey">C</span>ount:
|
||||
</label>
|
||||
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
|
||||
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% if (tunnelQuantity > 3) {
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
<div id="backupField" class="rowItem">
|
||||
<label for="tunnelBackupQuantity" accesskey="b">
|
||||
<span class="accessKey">B</span>ackup Count:
|
||||
</label>
|
||||
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
|
||||
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
|
||||
<% if (tunnelBackupQuantity > 3) {
|
||||
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label>I2CP Options:</label>
|
||||
</div>
|
||||
<div id="optionsHostField" class="rowItem">
|
||||
<label for="clientHost" accesskey="o">
|
||||
H<span class="accessKey">o</span>st:
|
||||
</label>
|
||||
<input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="optionsPortField" class="rowItem">
|
||||
<label for="clientPort" accesskey="r">
|
||||
Po<span class="accessKey">r</span>t:
|
||||
</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="customOptionsField" class="rowItem">
|
||||
<label for="customOptions" accesskey="u">
|
||||
C<span class="accessKey">u</span>stom options:
|
||||
</label>
|
||||
<input type="text" id="customOptions" name="customOptions" size="60" title="Custom Options" value="<%=editBean.getCustomOptions(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
</div>
|
||||
</div>
|
||||
<div id="globalOperationsPanel" class="panel">
|
||||
<div class="header"></div>
|
||||
<div class="footer">
|
||||
<div class="toolbox">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,184 +1,259 @@
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean"%><?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
|
||||
<jsp:setProperty name="indexBean" property="*" />
|
||||
|
||||
<html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
<title>I2PTunnel Webmanager - List</title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
|
||||
|
||||
<% if (indexBean.allowCSS()) {
|
||||
%><link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
|
||||
<link href="<%=indexBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="<%=indexBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
|
||||
<% }
|
||||
%>
|
||||
</head>
|
||||
<body style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td nowrap="true"><b>New Messages: </b><br />
|
||||
<a href="index.jsp">refresh</a>
|
||||
</td>
|
||||
<td>
|
||||
<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<body id="tunnelListPage">
|
||||
<div id="pageHeader">
|
||||
</div>
|
||||
|
||||
<div id="statusMessagePanel" class="panel">
|
||||
<div class="header">
|
||||
<h4>Status Messages</h4>
|
||||
</div>
|
||||
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="7" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Client Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name:</b></td>
|
||||
<td><b>Port:</b></td>
|
||||
<td><b>Type:</b></td>
|
||||
<td><b>Interface:</b></td>
|
||||
<td><b>Status:</b></td>
|
||||
</tr>
|
||||
<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
|
||||
if (!indexBean.isClient(curClient)) continue; %>
|
||||
<tr>
|
||||
<td valign="top" align="left">
|
||||
<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curClient)) {
|
||||
case IndexBean.STARTING:
|
||||
%><b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
|
||||
break;
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr><td align="right" valign="top">Destination:</td>
|
||||
<td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
|
||||
<tr>
|
||||
<td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="5" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Server Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Points at:</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Status:</b>
|
||||
</td>
|
||||
</tr>
|
||||
<textarea id="statusMessages" rows="3" cols="60" readonly="readonly"><jsp:getProperty name="indexBean" property="messages" /></textarea>
|
||||
|
||||
<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
|
||||
if (indexBean.isClient(curServer)) continue; %>
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
|
||||
</td>
|
||||
<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
|
||||
<td valign="top" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curServer)) {
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
|
||||
%> (<a href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>">preview</a>)<%
|
||||
}
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
|
||||
break;
|
||||
case IndexBean.STARTING:
|
||||
%>
|
||||
<b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
break;
|
||||
}
|
||||
%>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
|
||||
<% } %>
|
||||
<div class="footer">
|
||||
<div class="toolbox">
|
||||
<a class="control" href="index.jsp">Refresh</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center" valign="middle">
|
||||
<b>Operations Menu - Please chose from below!</b><br /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<form action="index.jsp" method="GET">
|
||||
<td >
|
||||
<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
|
||||
<input type="submit" name="action" value="Stop all tunnels" />
|
||||
<input type="submit" name="action" value="Start all tunnels" />
|
||||
<input type="submit" name="action" value="Restart all" />
|
||||
<input type="submit" name="action" value="Reload config" />
|
||||
</td>
|
||||
</form>
|
||||
<form action="edit.jsp">
|
||||
<td >
|
||||
<b>Add new:</b>
|
||||
<select name="type">
|
||||
<option value="httpclient">HTTP proxy</option>
|
||||
<option value="client">Client tunnel</option>
|
||||
<option value="server">Server tunnel</option>
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="Create" />
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="localClientTunnelList" class="panel">
|
||||
<div class="header">
|
||||
<h4>Local Client Tunnels</h4>
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div class="nameHeaderField rowItem">
|
||||
<label>Name:</label>
|
||||
</div>
|
||||
<div class="portHeaderField rowItem">
|
||||
<label>Port:</label>
|
||||
</div>
|
||||
<div class="typeHeaderField rowItem">
|
||||
<label>Type:</label>
|
||||
</div>
|
||||
<div class="interfaceHeaderField rowItem">
|
||||
<label>Interface:</label>
|
||||
</div>
|
||||
<div class="statusHeaderField rowItem">
|
||||
<label>Status:</label>
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
<%
|
||||
for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
|
||||
if (!indexBean.isClient(curClient)) continue;
|
||||
%>
|
||||
<div class="nameField rowItem">
|
||||
<label>Name:</label>
|
||||
<span class="text"><a href="edit.jsp?tunnel=<%=curClient%>" title="Edit Tunnel Settings for <%=indexBean.getTunnelName(curClient)%>"><%=indexBean.getTunnelName(curClient)%></a></span>
|
||||
</div>
|
||||
<div class="portField rowItem">
|
||||
<label>Port:</label>
|
||||
<span class="text"><%=indexBean.getClientPort(curClient)%></span>
|
||||
</div>
|
||||
<div class="typeField rowItem">
|
||||
<label>Type:</label>
|
||||
<span class="text"><%=indexBean.getTunnelType(curClient)%></span>
|
||||
</div>
|
||||
<div class="interfaceField rowItem">
|
||||
<label>Interface:</label>
|
||||
<span class="text"><%=indexBean.getClientInterface(curClient)%></span>
|
||||
</div>
|
||||
<div class="statusField rowItem">
|
||||
<label>Status:</label>
|
||||
<%
|
||||
switch (indexBean.getTunnelStatus(curClient)) {
|
||||
case IndexBean.STARTING:
|
||||
%><div class="statusStarting text">Starting...</div>
|
||||
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">Stop</a>
|
||||
<%
|
||||
break;
|
||||
case IndexBean.RUNNING:
|
||||
%><div class="statusRunning text">Running</div>
|
||||
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">Stop</a>
|
||||
<%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><div class="statusNotRunning text">Stopped</div>
|
||||
<a class="control" title="Start this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">Start</a>
|
||||
<%
|
||||
break;
|
||||
}
|
||||
%></div>
|
||||
|
||||
<div class="destinationField rowItem">
|
||||
<label>Destination:</label>
|
||||
<input class="freetext" size="40" readonly="readonly" value="<%=indexBean.getClientDestination(curClient)%>" />
|
||||
</div>
|
||||
|
||||
<div class="descriptionField rowItem">
|
||||
<label>Description:</label>
|
||||
<div class="text"><%=indexBean.getTunnelDescription(curClient)%></div>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<form id="addNewClientTunnelForm" action="edit.jsp">
|
||||
<div class="toolbox">
|
||||
<label>Add new client tunnel:</label>
|
||||
<select name="type">
|
||||
<option value="client">Standard</option>
|
||||
<option value="httpclient">HTTP</option>
|
||||
<option value="ircclient">IRC</option>
|
||||
</select>
|
||||
<input class="control" type="submit" value="Create" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="localServerTunnelList" class="panel">
|
||||
<div class="header">
|
||||
<h4>Local Server Tunnels</h4>
|
||||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div class="nameHeaderField rowItem">
|
||||
<label>Name:</label>
|
||||
</div>
|
||||
<div class="targetHeaderField rowItem">
|
||||
<label>Points at:</label>
|
||||
</div>
|
||||
<div class="previewHeaderField rowItem">
|
||||
<label>Preview:</label>
|
||||
</div>
|
||||
<div class="statusHeaderField rowItem">
|
||||
<label>Status:</label>
|
||||
</div>
|
||||
|
||||
<%
|
||||
for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
|
||||
if (indexBean.isClient(curServer)) continue;
|
||||
|
||||
%>
|
||||
<div class="nameField rowItem">
|
||||
<label>Name:</label>
|
||||
<span class="text"><a href="edit.jsp?tunnel=<%=curServer%>" title="Edit Server Tunnel Settings for <%=indexBean.getTunnelName(curServer)%>"><%=indexBean.getTunnelName(curServer)%></a></span>
|
||||
</div>
|
||||
<div class="targetField rowItem">
|
||||
<label>Points at:</label>
|
||||
<span class="text"><%=indexBean.getServerTarget(curServer)%></span>
|
||||
</div>
|
||||
<div class="previewField rowItem">
|
||||
<%
|
||||
if ("httpserver".equals(indexBean.getInternalType(curServer)) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
|
||||
%><label>Preview:</label>
|
||||
<a class="control" title="Preview this Tunnel" href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>" target="_new">Preview</a>
|
||||
<%
|
||||
} else {
|
||||
%><span class="comment">No Preview</span>
|
||||
<%
|
||||
}
|
||||
%></div>
|
||||
<div class="statusField rowItem">
|
||||
<label>Status:</label>
|
||||
<%
|
||||
switch (indexBean.getTunnelStatus(curServer)) {
|
||||
case IndexBean.STARTING:
|
||||
%><div class="statusStarting text">Starting...</div>
|
||||
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">Stop</a>
|
||||
<%
|
||||
break;
|
||||
case IndexBean.RUNNING:
|
||||
%><div class="statusRunning text">Running</div>
|
||||
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">Stop</a>
|
||||
<%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><div class="statusNotRunning text">Stopped</div>
|
||||
<a class="control" title="Start this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">Start</a>
|
||||
<%
|
||||
break;
|
||||
}
|
||||
%></div>
|
||||
|
||||
<div class="descriptionField rowItem">
|
||||
<label>Description:</label>
|
||||
<div class="text"><%=indexBean.getTunnelDescription(curServer)%></div>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<div class="separator">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<form id="addNewServerTunnelForm" action="edit.jsp">
|
||||
<div class="toolbox">
|
||||
<label>Add new server tunnel:</label>
|
||||
<select name="type">
|
||||
<option value="server">Standard</option>
|
||||
<option value="httpserver">HTTP</option>
|
||||
</select>
|
||||
<input class="control" type="submit" value="Create" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="globalOperationsPanel" class="panel">
|
||||
<div class="header"></div>
|
||||
<div class="footer">
|
||||
<div class="toolbox">
|
||||
<a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=Stop%20all">Stop All</a><a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=Start%20all">Start All</a><a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=Restart%20all">Restart All</a><a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=Reload%20configuration">Reload Config</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
apps/jdom/jdom.jar
Normal file
BIN
apps/jdom/jdom.jar
Normal file
Binary file not shown.
1
apps/jdom/readme.txt
Normal file
1
apps/jdom/readme.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is JDOM 1.0 from http://jdom.org/, released under an Apache style license
|
||||
@@ -3,30 +3,29 @@
|
||||
|
||||
<target name="all" depends="build" />
|
||||
<target name="fetchJettylib" >
|
||||
<available property="jetty.available" file="jetty-5.1.2.zip" />
|
||||
<available property="jetty.available" file="jetty-5.1.6.zip" />
|
||||
<ant target="doFetchJettylib" />
|
||||
</target>
|
||||
<target name="doFetchJettylib" unless="jetty.available" >
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.2" />
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.6" />
|
||||
<echo message="distribution (http://jetty.mortbay.org/). These are not " />
|
||||
<echo message="necessary for using I2P, but are used by some applications on top of I2P," />
|
||||
<echo message="such as the routerconsole." />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.2.zip" verbose="true" dest="jetty-5.1.2.zip" />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.6.zip" verbose="true" dest="jetty-5.1.6.zip" />
|
||||
<ant target="doExtract" />
|
||||
</target>
|
||||
<target name="doExtract">
|
||||
<unzip src="jetty-5.1.2.zip" dest="." />
|
||||
<unzip src="jetty-5.1.6.zip" dest="." />
|
||||
<mkdir dir="jettylib" />
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.2/lib">
|
||||
<fileset dir="jetty-5.1.6/lib">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.2/ext">
|
||||
<fileset dir="jetty-5.1.6/ext">
|
||||
<include name="ant.jar" />
|
||||
<include name="commons-el.jar" />
|
||||
<include name="commons-logging.jar" />
|
||||
<include name="jasper-compiler.jar" />
|
||||
<include name="jasper-runtime.jar" />
|
||||
<include name="javax.servlet.jar" />
|
||||
@@ -34,7 +33,9 @@
|
||||
<include name="xercesImpl.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<delete dir="jetty-5.1.2" />
|
||||
<!-- note the rename, to keep compat with old rev, since we only used the API anyway -->
|
||||
<copy file="jetty-5.1.6/ext/commons-logging-api.jar" tofile="jettylib/commons-logging.jar" />
|
||||
<delete dir="jetty-5.1.6" />
|
||||
</target>
|
||||
<target name="build" depends="fetchJettylib" />
|
||||
<target name="builddep" />
|
||||
|
||||
1
apps/rome/readme.txt
Normal file
1
apps/rome/readme.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is ROME 0.7 from http://rome.dev.java.net/, released under a BSD license
|
||||
BIN
apps/rome/rome-0.7.jar
Normal file
BIN
apps/rome/rome-0.7.jar
Normal file
Binary file not shown.
@@ -59,6 +59,8 @@ public class ConfigLoggingHelper {
|
||||
buf.append(prefix).append('=').append(level).append('\n');
|
||||
}
|
||||
buf.append("</textarea><br />\n");
|
||||
buf.append("<i>Add additional logging statements above. Example: net.i2p.router.tunnel=WARN</i><br>");
|
||||
buf.append("<i>Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN</i><br>");
|
||||
buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerTask;
|
||||
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerAndRekeyTask;
|
||||
|
||||
/**
|
||||
* Handler to deal with form submissions from the main config form and act
|
||||
@@ -31,6 +35,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _recheckReachabilityRequested;
|
||||
private boolean _timeSyncEnabled;
|
||||
private boolean _requireIntroductions;
|
||||
private boolean _hiddenMode;
|
||||
private boolean _dynamicKeys;
|
||||
private String _tcpPort;
|
||||
private String _udpPort;
|
||||
private String _inboundRate;
|
||||
@@ -62,6 +68,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
|
||||
public void setHiddenMode(String moo) { _hiddenMode = true; }
|
||||
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
_hostname = (hostname != null ? hostname.trim() : null);
|
||||
@@ -263,6 +271,28 @@ public class ConfigNetHandler extends FormHandler {
|
||||
addFormNotice("Updating bandwidth share percentage");
|
||||
}
|
||||
}
|
||||
|
||||
// If hidden mode value changes, restart is required
|
||||
if (_hiddenMode && "false".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
|
||||
_context.router().setConfigSetting(Router.PROP_HIDDEN, "true");
|
||||
_context.router().getRouterInfo().addCapability(RouterInfo.CAPABILITY_HIDDEN);
|
||||
addFormNotice("Gracefully restarting into Hidden Router Mode. Make sure you have no 0-1 length "
|
||||
+ "<a href=\"configtunnels.jsp\">tunnels!</a>");
|
||||
hiddenSwitch();
|
||||
}
|
||||
|
||||
if (!_hiddenMode && "true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
|
||||
_context.router().removeConfigSetting(Router.PROP_HIDDEN);
|
||||
_context.router().getRouterInfo().delCapability(RouterInfo.CAPABILITY_HIDDEN);
|
||||
addFormNotice("Gracefully restarting to exit Hidden Router Mode");
|
||||
hiddenSwitch();
|
||||
}
|
||||
|
||||
if (_dynamicKeys) {
|
||||
_context.router().setConfigSetting(Router.PROP_DYNAMIC_KEYS, "true");
|
||||
} else {
|
||||
_context.router().removeConfigSetting(Router.PROP_DYNAMIC_KEYS);
|
||||
}
|
||||
|
||||
if (_requireIntroductions) {
|
||||
_context.router().setConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS, "true");
|
||||
@@ -290,6 +320,12 @@ public class ConfigNetHandler extends FormHandler {
|
||||
addFormNotice("Soft restart complete");
|
||||
}
|
||||
}
|
||||
|
||||
private void hiddenSwitch() {
|
||||
// Full restart required to generate new keys
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
private void updateRates() {
|
||||
boolean updated = false;
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.router.transport.udp.UDPAddress;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
public class ConfigNetHelper {
|
||||
private RouterContext _context;
|
||||
@@ -63,6 +64,22 @@ public class ConfigNetHelper {
|
||||
return " checked ";
|
||||
}
|
||||
|
||||
public String getHiddenModeChecked() {
|
||||
String enabled = _context.getProperty(Router.PROP_HIDDEN, "false");
|
||||
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
|
||||
return " checked ";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getDynamicKeysChecked() {
|
||||
String enabled = _context.getProperty(Router.PROP_DYNAMIC_KEYS, "false");
|
||||
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
|
||||
return " checked ";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getRequireIntroductionsChecked() {
|
||||
short status = _context.commSystem().getReachabilityStatus();
|
||||
switch (status) {
|
||||
|
||||
@@ -33,6 +33,21 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateWrapperManagerAndRekeyTask implements Runnable {
|
||||
private int _exitCode;
|
||||
public UpdateWrapperManagerAndRekeyTask(int exitCode) {
|
||||
_exitCode = exitCode;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
Router.killKeys();
|
||||
WrapperManager.signalStopped(_exitCode);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processForm() {
|
||||
if (_action == null) return;
|
||||
@@ -56,6 +71,14 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
_context.router().shutdown(Router.EXIT_HARD_RESTART);
|
||||
addFormNotice("Hard restart requested");
|
||||
} else if ("Rekey and Restart".equals(_action)) {
|
||||
addFormNotice("Rekeying after graceful restart");
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
} else if ("Rekey and Shutdown".equals(_action)) {
|
||||
addFormNotice("Rekeying after graceful shutdown");
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL);
|
||||
} else if ("Run I2P on startup".equals(_action)) {
|
||||
installService();
|
||||
} else if ("Don't run I2P on startup".equals(_action)) {
|
||||
@@ -195,4 +218,4 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
addFormError("Error updating the client config");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ public class ConfigStatsHandler extends FormHandler {
|
||||
|
||||
if (_explicitFilter) {
|
||||
_stats.clear();
|
||||
if (_explicitFilterValue == null)
|
||||
_explicitFilterValue = "";
|
||||
|
||||
if (_explicitFilterValue.indexOf(',') != -1) {
|
||||
StringTokenizer tok = new StringTokenizer(_explicitFilterValue, ",");
|
||||
|
||||
@@ -135,7 +135,7 @@ public class ConfigTunnelsHandler extends FormHandler {
|
||||
if (saveRequired) {
|
||||
boolean saved = _context.router().saveConfig();
|
||||
if (saved)
|
||||
addFormNotice("Configuration saved successfully");
|
||||
addFormNotice("Exploratory tunnel configuration saved successfully");
|
||||
else
|
||||
addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class JobQueueHelper {
|
||||
private RouterContext _context;
|
||||
private Writer _out;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
* @param contextId begging few characters of the routerHash, or null to pick
|
||||
* the first one we come across.
|
||||
*/
|
||||
public void setContextId(String contextId) {
|
||||
try {
|
||||
_context = ContextHelper.getContext(contextId);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public JobQueueHelper() {}
|
||||
|
||||
public void setWriter(Writer writer) { _out = writer; }
|
||||
|
||||
public String getJobQueueSummary() {
|
||||
try {
|
||||
if (_out != null) {
|
||||
_context.jobQueue().renderStatusHTML(_out);
|
||||
return "";
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
_context.jobQueue().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
return new String(baos.toByteArray());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ public class LogsHelper {
|
||||
buf.append("<code>\n");
|
||||
for (int i = msgs.size(); i > 0; i--) {
|
||||
String msg = (String)msgs.get(i - 1);
|
||||
msg = msg.replaceAll("<","<");
|
||||
buf.append("<li>");
|
||||
buf.append(msg);
|
||||
buf.append("</li>\n");
|
||||
@@ -46,6 +47,7 @@ public class LogsHelper {
|
||||
buf.append("<code>\n");
|
||||
for (int i = msgs.size(); i > 0; i--) {
|
||||
String msg = (String)msgs.get(i - 1);
|
||||
msg = msg.replaceAll("<","<");
|
||||
buf.append("<li>");
|
||||
buf.append(msg);
|
||||
buf.append("</li>\n");
|
||||
@@ -59,8 +61,10 @@ public class LogsHelper {
|
||||
String str = FileUtil.readTextFile("wrapper.log", 500, false);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
else {
|
||||
str = str.replaceAll("<","<");
|
||||
return "<pre>" + str + "</pre>";
|
||||
}
|
||||
}
|
||||
|
||||
public String getConnectionLogs() {
|
||||
|
||||
@@ -198,7 +198,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
// ignore
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public class PeerHelper {
|
||||
public String getPeerSummary() {
|
||||
try {
|
||||
_context.commSystem().renderStatusHTML(_out);
|
||||
_context.bandwidthLimiter().renderStatusHTML(_out);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -28,14 +28,19 @@ public class ReseedHandler {
|
||||
if (nonce == null) return;
|
||||
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
|
||||
nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.noncePrev"))) {
|
||||
synchronized (_reseedRunner) {
|
||||
if (_reseedRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
|
||||
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
|
||||
reseed.start();
|
||||
}
|
||||
requestReseed();
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestReseed() {
|
||||
synchronized (_reseedRunner) {
|
||||
if (_reseedRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
|
||||
System.out.println("Reseeding");
|
||||
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
|
||||
reseed.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +51,8 @@ public class ReseedHandler {
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
reseed();
|
||||
reseed(false);
|
||||
System.out.println("Reseeding complete");
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
@@ -59,7 +65,7 @@ public class ReseedHandler {
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private static void reseed() {
|
||||
private static void reseed(boolean echoStatus) {
|
||||
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
|
||||
seedURL = DEFAULT_SEED_URL;
|
||||
@@ -85,10 +91,16 @@ public class ReseedHandler {
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
if (echoStatus) {
|
||||
System.out.print(".");
|
||||
if (fetched % 60 == 0)
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if (echoStatus) System.out.println();
|
||||
} catch (Throwable t) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
|
||||
}
|
||||
@@ -172,7 +184,11 @@ public class ReseedHandler {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
reseed();
|
||||
//System.out.println("Done reseeding");
|
||||
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {
|
||||
System.out.println("Not reseeding, as requested");
|
||||
return; // not reseeding on request
|
||||
}
|
||||
System.out.println("Reseeding");
|
||||
reseed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,24 @@ public class RouterConsoleRunner {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
// we check the i2p installation directory (.) for a flag telling us not to reseed,
|
||||
// but also check the home directory for that flag too, since new users installing i2p
|
||||
// don't have an installation directory that they can put the flag in yet.
|
||||
File noReseedFile = new File(new File(System.getProperty("user.home")), ".i2pnoreseed");
|
||||
File noReseedFileAlt1 = new File(new File(System.getProperty("user.home")), "noreseed.i2p");
|
||||
File noReseedFileAlt2 = new File(".i2pnoreseed");
|
||||
File noReseedFileAlt3 = new File("noreseed.i2p");
|
||||
if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
|
||||
File netDb = new File("netDb");
|
||||
// sure, some of them could be "my.info" or various leaseSet- files, but chances are,
|
||||
// if someone has those files, they've already been seeded (at least enough to let them
|
||||
// get i2p started - they can reseed later in the web console)
|
||||
String names[] = (netDb.exists() ? netDb.list() : null);
|
||||
if ( (names == null) || (names.length < 15) ) {
|
||||
ReseedHandler.requestReseed();
|
||||
}
|
||||
}
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
|
||||
I2PThread t = new I2PThread(fetcher, "NewsFetcher");
|
||||
|
||||
@@ -83,19 +83,28 @@ public class SummaryHelper {
|
||||
|
||||
long ms = _context.clock().getOffset();
|
||||
|
||||
if (ms < 60 * 1000) {
|
||||
return now + " (" + (ms / 1000) + "s)";
|
||||
} else if (ms < 60 * 60 * 1000) {
|
||||
return now + " (" + (ms / (60 * 1000)) + "m)";
|
||||
} else if (ms < 24 * 60 * 60 * 1000) {
|
||||
return now + " (" + (ms / (60 * 60 * 1000)) + "h)";
|
||||
long diff = ms;
|
||||
if (diff < 0)
|
||||
diff = 0 - diff;
|
||||
if (diff == 0) {
|
||||
return now + " (no skew)";
|
||||
} else if (diff < 1000) {
|
||||
return now + " (" + ms + "ms skew)";
|
||||
} else if (diff < 5 * 1000) {
|
||||
return now + " (" + (ms / 1000) + "s skew)";
|
||||
} else if (diff < 60 * 1000) {
|
||||
return now + " <b>(" + (ms / 1000) + "s skew)</b>";
|
||||
} else if (diff < 60 * 60 * 1000) {
|
||||
return now + " <b>(" + (ms / (60 * 1000)) + "m skew)</b>";
|
||||
} else if (diff < 24 * 60 * 60 * 1000) {
|
||||
return now + " <b>(" + (ms / (60 * 60 * 1000)) + "h skew)</b>";
|
||||
} else {
|
||||
return now + " (" + (ms / (24 * 60 * 60 * 1000)) + "d)";
|
||||
return now + " <b>(" + (ms / (24 * 60 * 60 * 1000)) + "d skew)</b>";
|
||||
}
|
||||
}
|
||||
|
||||
public boolean allowReseed() {
|
||||
return (_context.netDb().getKnownRouters() < 10);
|
||||
return (_context.netDb().getKnownRouters() < 30);
|
||||
}
|
||||
|
||||
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
|
||||
@@ -108,7 +117,7 @@ public class SummaryHelper {
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
return "ERR-SymmetricNAT";
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
return "ERR-Reject";
|
||||
return "OK (NAT)";
|
||||
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
|
||||
default:
|
||||
return "Unknown";
|
||||
@@ -205,11 +214,7 @@ public class SummaryHelper {
|
||||
public String getInboundMinuteKBps() {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
double kbps = rate.getAverageValue()/1024;
|
||||
double kbps = _context.bandwidthLimiter().getReceiveBps()/1024d;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
@@ -221,11 +226,7 @@ public class SummaryHelper {
|
||||
public String getOutboundMinuteKBps() {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("bw.sendRate");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
double kbps = rate.getAverageValue()/1024;
|
||||
double kbps = _context.bandwidthLimiter().getSendBps()/1024d;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
@@ -416,6 +417,28 @@ public class SummaryHelper {
|
||||
return _context.tunnelManager().getOutboundTunnelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many inbound client tunnels we have.
|
||||
*
|
||||
*/
|
||||
public int getInboundClientTunnels() {
|
||||
if (_context == null)
|
||||
return 0;
|
||||
else
|
||||
return _context.tunnelManager().getInboundClientTunnelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many active outbound client tunnels we have.
|
||||
*
|
||||
*/
|
||||
public int getOutboundClientTunnels() {
|
||||
if (_context == null)
|
||||
return 0;
|
||||
else
|
||||
return _context.tunnelManager().getOutboundClientTunnelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many tunnels we are participating in.
|
||||
*
|
||||
@@ -467,4 +490,4 @@ public class SummaryHelper {
|
||||
public boolean updateAvailable() {
|
||||
return NewsFetcher.getInstance(_context).updateAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ public class UpdateHandler {
|
||||
|
||||
private static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
|
||||
|
||||
public UpdateHandler() {}
|
||||
public UpdateHandler() {
|
||||
this(ContextHelper.getContext(null));
|
||||
}
|
||||
public UpdateHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UpdateHandler.class);
|
||||
@@ -94,7 +96,7 @@ public class UpdateHandler {
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
update();
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.updateInProgress", "false");
|
||||
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
private void update() {
|
||||
@@ -144,7 +146,7 @@ public class UpdateHandler {
|
||||
buf.append(" transferred<br />");
|
||||
_status = buf.toString();
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
_status = "<b>Update downloaded</b><br />";
|
||||
TrustedUpdate up = new TrustedUpdate(_context);
|
||||
boolean ok = up.migrateVerified(RouterVersion.VERSION, SIGNED_UPDATE_FILE, "i2pupdate.zip");
|
||||
|
||||
@@ -29,10 +29,14 @@
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
|
||||
<b>External UDP address:</b> <i><jsp:getProperty name="nethelper" property="udpAddress" /></i><br />
|
||||
<b>Require SSU introductions through NAT hole punching? </b>
|
||||
<b>Require SSU introductions? </b>
|
||||
<input type="checkbox" name="requireIntroductions" value="true" <jsp:getProperty name="nethelper" property="requireIntroductionsChecked" /> /><br />
|
||||
<p>If you can't poke a hole in your NAT or firewall to allow unsolicited UDP packets to reach the
|
||||
router, as detected with the <i>Status: ERR-Reject</i>, then you will need SSU introductions.
|
||||
<p>If you can, please poke a hole in your NAT or firewall to allow unsolicited UDP packets to reach
|
||||
you on your external UDP address. If you can't, I2P now includes supports UDP hole punching
|
||||
with "SSU introductions" - peers who will relay a request from someone you don't know to your
|
||||
router for your router so that you can make an outbound connection to them. I2P will use these
|
||||
introductions automatically if it detects that the port is not forwarded (as shown by
|
||||
the <i>Status: OK (NAT)</i> line), or you can manually require them here.
|
||||
Users behind symmetric NATs, such as OpenBSD's pf, are not currently supported.</p>
|
||||
<input type="submit" name="recheckReachability" value="Check network reachability..." />
|
||||
<hr />
|
||||
@@ -54,6 +58,23 @@
|
||||
<jsp:getProperty name="nethelper" property="sharePercentageBox" /><br />
|
||||
Sharing a higher percentage will improve your anonymity and help the network
|
||||
<hr />
|
||||
<b>Dynamic Router Keys: </b>
|
||||
<input type="checkbox" name="dynamicKeys" value="true" <jsp:getProperty name="nethelper" property="dynamicKeysChecked" /> /><br />
|
||||
<p>
|
||||
This setting causes your router identity to be regenerated every time your IP address
|
||||
changes. If you have a dynamic IP this option can speed up your reintegration into
|
||||
the network (since people will have shitlisted your old router identity), and, for
|
||||
very weak adversaries, help frustrate trivial
|
||||
<a href="http://www.i2p.net/how_threatmodel#intersection">intersection
|
||||
attacks</a> against the NetDB. Your different router identities would only be
|
||||
'hidden' among other I2P users at your ISP, and further analysis would link
|
||||
the router identities further.</p>
|
||||
<p>Note that when I2P detects an IP address change, it will automatically
|
||||
initiate a restart in order to rekey and to disconnect from peers before they
|
||||
update their profiles - any long lasting client connections will be disconnected,
|
||||
though such would likely already be the case anyway, since the IP address changed.
|
||||
</p>
|
||||
<hr />
|
||||
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
<jsp:getProperty name="tunnelshelper" property="form" />
|
||||
<hr />
|
||||
<i>Note - Exploratory tunnel setting changes are stored in the router.config file.</i></br>
|
||||
<i>Client tunnel changes are temporary and are not saved.</i><br>
|
||||
<i>To make permanent client tunnel changes see the </i><a href="i2ptunnel/index.jsp">i2ptunnel page</a>.<br>
|
||||
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,10 @@ div.logo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.toolbar {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
div.routersummary {
|
||||
/* width: 8em; */
|
||||
/* height: 5em; */
|
||||
|
||||
21
apps/routerconsole/jsp/jobs.jsp
Normal file
21
apps/routerconsole/jsp/jobs.jsp
Normal file
@@ -0,0 +1,21 @@
|
||||
<%@page contentType="text/html"%>
|
||||
<%@page pageEncoding="UTF-8"%>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - job queue</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<%@include file="nav.jsp" %>
|
||||
<%@include file="summary.jsp" %>
|
||||
|
||||
<div class="main" id="main">
|
||||
<jsp:useBean class="net.i2p.router.web.JobQueueHelper" id="jobQueueHelper" scope="request" />
|
||||
<jsp:setProperty name="jobQueueHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:setProperty name="jobQueueHelper" property="writer" value="<%=out%>" />
|
||||
<jsp:getProperty name="jobQueueHelper" property="jobQueueSummary" />
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,4 @@
|
||||
<%@page import="java.io.File" %>
|
||||
<% response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Cache-Control","no-cache");
|
||||
response.setDateHeader("Expires", 0);
|
||||
@@ -13,22 +14,32 @@
|
||||
<a href="index.jsp"><img src="i2plogo.png" alt="Router Console" width="187" height="35" /></a><br />
|
||||
[<a href="config.jsp">configuration</a> | <a href="help.jsp">help</a>]
|
||||
</div>
|
||||
|
||||
<h4>
|
||||
<div class="toolbar">
|
||||
<% if (new File("docs/toolbar.html").exists()) { %>
|
||||
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="toolbarhelper" scope="request" />
|
||||
<jsp:setProperty name="toolbarhelper" property="page" value="docs/toolbar.html" />
|
||||
<jsp:setProperty name="toolbarhelper" property="maxLines" value="300" />
|
||||
<jsp:getProperty name="toolbarhelper" property="content" />
|
||||
<% } else { %>
|
||||
<!-- Could not find docs/toolbar.html! -->
|
||||
<a href="susimail/susimail">Susimail</a> |
|
||||
<a href="susidns/">SusiDNS</a> |
|
||||
<a href="susidns/index.jsp">SusiDNS</a> |
|
||||
<a href="syndie/">Syndie</a> |
|
||||
<a href="i2ptunnel/">I2PTunnel</a> |
|
||||
<a href="i2psnark/">I2PSnark</a> |
|
||||
<a href="http://localhost:7658/">My Eepsite</a> <br>
|
||||
<a href="i2ptunnel/index.jsp">I2PTunnel</a> |
|
||||
<a href="tunnels.jsp">Tunnels</a> |
|
||||
<a href="profiles.jsp">Profiles</a> |
|
||||
<a href="netdb.jsp">NetDB</a> |
|
||||
<a href="logs.jsp">Logs</a> |
|
||||
<a href="jobs.jsp">Jobs</a> |
|
||||
<a href="oldstats.jsp">Stats</a> |
|
||||
<a href="oldconsole.jsp">Internals</a>
|
||||
<% } %>
|
||||
<jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
|
||||
<jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:getProperty name="navhelper" property="clientAppLinks" />
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.NoticeHelper" id="noticehelper" scope="request" />
|
||||
<jsp:setProperty name="noticehelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
%><hr />
|
||||
|
||||
<u><b>Bandwidth in/out</b></u><br />
|
||||
<b>1m:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
|
||||
<b>1s:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
|
||||
<b>5m:</b> <jsp:getProperty name="helper" property="inboundFiveMinuteKBps" />/<jsp:getProperty name="helper" property="outboundFiveMinuteKBps" />KBps<br />
|
||||
<b>Total:</b> <jsp:getProperty name="helper" property="inboundLifetimeKBps" />/<jsp:getProperty name="helper" property="outboundLifetimeKBps" />KBps<br />
|
||||
<b>Used:</b> <jsp:getProperty name="helper" property="inboundTransferred" />/<jsp:getProperty name="helper" property="outboundTransferred" /><br />
|
||||
@@ -72,9 +72,9 @@
|
||||
|
||||
<jsp:getProperty name="helper" property="destinations" />
|
||||
|
||||
<u><b>Tunnels</b></u><br />
|
||||
<b>Inbound:</b> <jsp:getProperty name="helper" property="inboundTunnels" /><br />
|
||||
<b>Outbound:</b> <jsp:getProperty name="helper" property="outboundTunnels" /><br />
|
||||
<u><b>Tunnels in/out</b></u><br />
|
||||
<b>Exploratory:</b> <jsp:getProperty name="helper" property="inboundTunnels" />/<jsp:getProperty name="helper" property="outboundTunnels" /><br />
|
||||
<b>Client:</b> <jsp:getProperty name="helper" property="inboundClientTunnels" />/<jsp:getProperty name="helper" property="outboundClientTunnels" /><br />
|
||||
<b>Participating:</b> <jsp:getProperty name="helper" property="participatingTunnels" /><br />
|
||||
<hr />
|
||||
|
||||
|
||||
14
apps/routerconsole/jsp/viewtheme.jsp
Normal file
14
apps/routerconsole/jsp/viewtheme.jsp
Normal file
@@ -0,0 +1,14 @@
|
||||
<%
|
||||
String uri = request.getRequestURI();
|
||||
if (uri.endsWith(".css")) {
|
||||
response.setContentType("text/css");
|
||||
} else if (uri.endsWith(".png")) {
|
||||
response.setContentType("image/png");
|
||||
} else if (uri.endsWith(".gif")) {
|
||||
response.setContentType("image/gif");
|
||||
} else if (uri.endsWith(".jpg")) {
|
||||
response.setContentType("image/jpeg");
|
||||
}
|
||||
|
||||
net.i2p.util.FileUtil.readFile(uri, "./docs", response.getOutputStream());
|
||||
%>
|
||||
@@ -5,6 +5,13 @@
|
||||
|
||||
<web-app>
|
||||
<!-- precompiled servlets -->
|
||||
|
||||
<!-- yeah, i'm lazy, using a jsp instead of a servlet.. -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.router.web.jsp.viewtheme_jsp</servlet-name>
|
||||
<url-pattern>/themes/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<session-config>
|
||||
<session-timeout>
|
||||
30
|
||||
|
||||
@@ -90,6 +90,7 @@ void string_copy(string_t src,string_t dest) {
|
||||
void string_copy_raw(string_t src, void* dest,size_t size) {
|
||||
size = min(src->size,size);
|
||||
memcpy(dest,src->data,size);
|
||||
((char*)dest)[size] = '\0';
|
||||
}
|
||||
|
||||
const char* string_data(string_t self) {
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
## Copyright 2004 Brian Ristuccia. This program is Free Software;
|
||||
## You can redistribute it and/or modify it under the same terms as
|
||||
## Perl itself.
|
||||
|
||||
package Net::SAM;
|
||||
|
||||
@ISA = ( "IO::Socket::INET" );
|
||||
|
||||
use strict;
|
||||
|
||||
use POSIX;
|
||||
|
||||
use Switch;
|
||||
|
||||
use IO::Socket;
|
||||
use IO::Select;
|
||||
|
||||
#use Net::SAM::StreamSession;
|
||||
#use Net::SAM::DatagramSession;
|
||||
#use Net::SAM::RawSession;
|
||||
|
||||
sub new {
|
||||
my ($class) = shift;
|
||||
my $type = ref($class) || $class;
|
||||
my $self = $type->SUPER::new("127.0.0.1:7656");
|
||||
|
||||
|
||||
${*$self}->{incomingraw} = [];
|
||||
|
||||
# Connect us to the local SAM proxy.
|
||||
# my $samsock = IO::Socket::INET->new('127.0.0.1:7657');
|
||||
#$self->{samsock}=$samsock;
|
||||
|
||||
# Say hello, read response.
|
||||
$self->SUPER::send("HELLO VERSION MIN=1.0 MAX=1.0\n");
|
||||
|
||||
while (! ${*$self}->{greeted}) {
|
||||
$self->readprocess();
|
||||
}
|
||||
print "Created SAM object\n";
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub lookup {
|
||||
my $self = shift;
|
||||
my $name= shift;
|
||||
|
||||
$self->SUPER::send("NAMING LOOKUP NAME=$name\n");
|
||||
undef ${*$self}->{RESULT};
|
||||
while (! ${*$self}->{RESULT}) {
|
||||
$self->readprocess();
|
||||
}
|
||||
if ( ${*$self}->{RESULT} == "OK" ) {
|
||||
return ${*$self}->{VALUE};
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#sub createsession {
|
||||
# my ($self) = shift;
|
||||
# my ($sesstype) = shift;
|
||||
# print $self->{samsock} "SESSION CREATE STYLE=$SESSTYPE DESTINATION=$DEST, DIRECTION=
|
||||
#}
|
||||
|
||||
#sub waitfor {
|
||||
# my ($self) = shift;
|
||||
# my ($prefix) = shift;
|
||||
# my ($response) = <$samsock>;#
|
||||
|
||||
# if $response =~
|
||||
|
||||
|
||||
#}
|
||||
|
||||
|
||||
sub readprocesswrite {
|
||||
my $self = shift;
|
||||
$self->readprocess();
|
||||
$self->dowrite();
|
||||
}
|
||||
|
||||
sub doread {
|
||||
my $self = shift;
|
||||
my $rv;
|
||||
my $data;
|
||||
|
||||
$rv = $self->recv($data, $POSIX::BUFSIZE, 0);
|
||||
|
||||
if ( defined($rv) && ( length($data) >= 1 ) ) {
|
||||
# We received some data. Put it in our buffer.
|
||||
${*$self}->{inbuffer} += $data;
|
||||
} else {
|
||||
# No data. Either we're on a non-blocking socket, or there
|
||||
# was an error or EOF
|
||||
if ( $!{EAGAIN} ) {
|
||||
return 1;
|
||||
} else {
|
||||
# I suppose caller can look at $! for details
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub dowrite {
|
||||
my $self = shift;
|
||||
my $rv;
|
||||
my $data;
|
||||
|
||||
$rv = $self->send(${*$self}->{outbuffer}, 0);
|
||||
|
||||
if ( ! defined($rv) ) {
|
||||
warn "SAM::dowrite - Couldn't write for no apparent reason.\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
if ( $rv == length(${*$self}->{outbuffer}) || $!{EWOULDBLOCK} ) {
|
||||
substr(${*$self}->{outbuffer},0, $rv) = ''; # Remove from buffer
|
||||
|
||||
# Nuke buffer if empty
|
||||
delete ${*$self}->{outbuffer} unless length(${*$self}->{outbuffer});
|
||||
} else {
|
||||
# Socket closed on us or something?
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub messages {
|
||||
my $self = shift;
|
||||
|
||||
return @{ ${*$self}->{messages} };
|
||||
}
|
||||
|
||||
sub queuemessage {
|
||||
|
||||
my $self = shift;
|
||||
my $message = shift;
|
||||
|
||||
push @{ ${*$self}->{messages} } , $message;
|
||||
}
|
||||
|
||||
sub unqueuemessage {
|
||||
my $self = shift;
|
||||
|
||||
return unshift(@{ ${*$self}->{messages} } );
|
||||
|
||||
}
|
||||
|
||||
sub readprocess {
|
||||
my $self = shift;
|
||||
|
||||
$self->doread();
|
||||
$self->process();
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my %tvhash;
|
||||
my $payload;
|
||||
|
||||
|
||||
# Before we can read any new messages, if an existing message has payload
|
||||
# we must read it in. Otherwise we'll create garbage messages containing
|
||||
# the payload of previous messages.
|
||||
|
||||
if ( ${*$self}->{payloadrequired} >= 1 ) {
|
||||
|
||||
if ( length( ${*$self}->{inbuffer} ) >= ${*$self}->{payloadrequired} ) {
|
||||
# Scarf payload from inbuffer into $payload
|
||||
$payload = substr(${*$self}->{inbuffer}, 0,
|
||||
${*$self}->{payloadrequired});
|
||||
|
||||
# Nuke payload from inbuffer
|
||||
substr(${*$self}->{inbuffer}, 0,
|
||||
${*$self}->{payloadrequired} ) = '';
|
||||
|
||||
# Put message with payload into spool
|
||||
push @{ ${*$self}->{messages} } ,
|
||||
${*$self}->{messagerequiringpayload}.$payload;
|
||||
|
||||
# Delete the saved message requiring payload
|
||||
delete ${*$self}->{messagerequiringpayload};
|
||||
} else {
|
||||
# Insufficient payload in inbuffer. Try again later.
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( ${*$self}->{inbuffer} =~ s/(.*\n)// ) {
|
||||
%tvhash = $self->_hashtv($1); # Returns a tag/value hash
|
||||
if ( $tvhash{SIZE} ) {
|
||||
# We've got a message with payload on our hands. :(
|
||||
${*$self}->{payloadrequired} = $tvhash{SIZE};
|
||||
${*$self}->{messagerequiringpayload} = $1;
|
||||
return 1; # Could call ourself here, but we'll get called again.
|
||||
} else {
|
||||
push @{ ${*$self}->{messages} } , $1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# sub junk {
|
||||
|
||||
|
||||
# print "readprocess: " . $self->connected() . "\n";
|
||||
|
||||
# # May block if the SAM bridge gets hosed
|
||||
# my $response = <$self>;
|
||||
|
||||
# print "readprocess: $!" . $self->connected() . "\n";
|
||||
|
||||
# chomp $response;
|
||||
# my ($primative, $more, $extra) = split (' ', $response, 3);
|
||||
|
||||
# $primative = uc($primative);
|
||||
|
||||
# print "readprocess: " . $self->connected() . " -- $primative -- $more -- $extra\n";
|
||||
|
||||
# switch ($primative) {
|
||||
|
||||
# case "HELLO" {
|
||||
# if ($more !~ m/REPLY/ ) { die ("Bogus HELLO response") }
|
||||
# if ($extra =~ m/NOVERSION/ ) {
|
||||
# die("SAM Bridge Doesn't support my version") ;
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# ${*$self}->{greeted} = 1;
|
||||
# };
|
||||
# case "SESSION" {
|
||||
# if ( $more !~ m/STATUS/ ) {
|
||||
# die("Bogus SESSION response");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# }
|
||||
# case "STREAM" {};
|
||||
# case "DATAGRAM" {
|
||||
# if ( $more !~ m/RECEIVE/ ) {
|
||||
# die("Bogus DATAGRAM response.");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# push @{ ${*$self}->{incomingdatagram } },
|
||||
# [ ${*$self}->{DESTINATION},
|
||||
# $self->_readblock(${*$self}->{SIZE}) ];
|
||||
|
||||
# };
|
||||
# case "RAW" {
|
||||
# if ( $more !~ m/RECEIVE/ ) {
|
||||
# die("Bogus RAW response.");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
|
||||
# push @{ $self->{incomingraw} }, $self->_readblock($self->{SIZE});
|
||||
# };
|
||||
# case "NAMING" {
|
||||
# if ( $more !~ m/REPLY/ ) {
|
||||
# die("Bogus NAMING response");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# };
|
||||
# case "DEST" {};
|
||||
# }
|
||||
# return 1;
|
||||
# }
|
||||
|
||||
sub getfh {
|
||||
# Return the FH of the SAM socket so apps can select() or poll() on it
|
||||
my $self = shift;
|
||||
return $self->{samsock};
|
||||
}
|
||||
|
||||
sub _readblock {
|
||||
my $self = shift;
|
||||
my $size = shift;
|
||||
my $chunk;
|
||||
my $payload;
|
||||
|
||||
while ( $size > 1 ) {
|
||||
# XXX: May block. No error checking.
|
||||
print "readblock: $size\n";
|
||||
$size -= $self->SUPER::recv($chunk, $size);
|
||||
$payload .= $chunk;
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
|
||||
sub _hashtv {
|
||||
my $self = shift;
|
||||
my $tvstring = shift;
|
||||
my $tvhash;
|
||||
|
||||
while ( $tvstring =~ m/(\S+)=(\S+)/sg ) {
|
||||
$tvhash->{$1}=$2;
|
||||
print "hashtv: $1=$2\n"
|
||||
}
|
||||
return $tvhash;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
# Do nothing yet.
|
||||
}
|
||||
|
||||
#sub StreamSession {
|
||||
# my $self = shift;
|
||||
# return Net::SAM::StreamSession->new($self);
|
||||
#}
|
||||
|
||||
#sub DatagramSession {
|
||||
# return Net::SAM::DatagramSession->new($self);
|
||||
#}
|
||||
|
||||
#sub RawSession {
|
||||
# return Net::SAM::RawSession->new($self);
|
||||
#}
|
||||
|
||||
1;
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
package Net::SAM::DatagramSession;
|
||||
|
||||
use Net::SAM;
|
||||
|
||||
@ISA = ("Net::SAM");
|
||||
|
||||
sub new {
|
||||
my ($class) = shift;
|
||||
my ($dest , $direction, $options) = shift;
|
||||
|
||||
my $self = $class->SUPER::new(@_);
|
||||
|
||||
$self->SUPER::send("SESSION CREATE STYLE=DATAGRAM DESTINATION=$dest DIRECTION=$direction $options\n");
|
||||
|
||||
undef ${*$self}->{RESULT};
|
||||
|
||||
while ( ! ${*$self}->{RESULT} ) {
|
||||
$self->readprocess() || return undef;
|
||||
|
||||
}
|
||||
|
||||
if ( ${*$self}->{RESULT} == "OK" ) {
|
||||
return $self;
|
||||
} else {
|
||||
return undef; # sorry.
|
||||
}
|
||||
}
|
||||
|
||||
sub send {
|
||||
my $self = shift;
|
||||
my $destination = shift;
|
||||
my $message = shift;
|
||||
my $size = length($message);
|
||||
$self->SUPER::send("DATAGRAM SEND DESTINATION=$destination SIZE=$size\n$message");
|
||||
}
|
||||
|
||||
sub receive {
|
||||
my $self = shift;
|
||||
|
||||
# Shift one off the fifo array. Returns undef if none wait.
|
||||
return shift @{ $self->{incomingdatagram} };
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
package Net::SAM::RawSession;
|
||||
|
||||
use Net::SAM;
|
||||
|
||||
@ISA = ("Net::SAM");
|
||||
|
||||
sub new {
|
||||
my ($class) = shift;
|
||||
my ($dest , $direction, $options) = shift;
|
||||
|
||||
my $self = $class->SUPER::new(@_);
|
||||
|
||||
$self->send("SESSION CREATE STYLE=RAW DESTINATION=$dest DIRECTION=$direction $options\n");
|
||||
|
||||
undef $self->{result};
|
||||
while ( ! $self->{RESULT} ) {
|
||||
$self->readprocess();
|
||||
}
|
||||
|
||||
if ( $self->{RESULT} == "OK" ) {
|
||||
return $self;
|
||||
} else {
|
||||
return 0; # sorry.
|
||||
}
|
||||
}
|
||||
|
||||
sub send {
|
||||
my $self = shift;
|
||||
my $destination = shift;
|
||||
my $message = shift;
|
||||
my $samsock = $self->{samsock};
|
||||
my $size = length($message);
|
||||
print $samsock "RAW SEND DESTINATION=$destination SIZE=$size\n$message";
|
||||
}
|
||||
|
||||
sub receive {
|
||||
my $self = shift;
|
||||
|
||||
# Shift one off the fifo array. Returns undef if none wait.
|
||||
return shift @{ $self->{incomingraw} };
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
1;
|
||||
108
apps/sam/perl/README
Normal file
108
apps/sam/perl/README
Normal file
@@ -0,0 +1,108 @@
|
||||
# BASIC Perl SAM Module
|
||||
# created 2005 by postman (postman@i2pmail.org)
|
||||
|
||||
|
||||
1. What does it do?
|
||||
|
||||
The SAM module is a little Perl add-on that - on one side -
|
||||
establishes communication with a I2P router's (http://www.i2p.net) SAM bridge
|
||||
(Simple anonymous messaging ( http://www.i2p.net/sam)). On the
|
||||
other side it exposes a simple socket like interface to the user.
|
||||
Over this interface the user can send or receive datastreams from I2P
|
||||
destinations as if he would communicate with a normal IP socket.
|
||||
|
||||
The SAM module can be integrated into perl scripts that
|
||||
want to communicate with I2P services.
|
||||
|
||||
|
||||
2. Is this code usable?
|
||||
|
||||
This perl module should be considered as proof-of-concept
|
||||
quality. It did surely work for me and my test setups, but
|
||||
it might not work at all on your system. If you run into problems
|
||||
you can contact me.
|
||||
|
||||
|
||||
3. Does ist support DATAGRAM and RAW sessions?
|
||||
|
||||
No, at the moment the module only supports STREAM sessions.
|
||||
Support for other session types might be added in the future.
|
||||
|
||||
|
||||
4. How to install it?
|
||||
Create a Subfolder called I2P in your Perl Installation's Net Module
|
||||
folder (i.e. /usr/lib/perl5/5.8.4./Net/I2P ) and copy the module there.
|
||||
You can now use it with use Net::I2P::SAM.
|
||||
The module only depends on Net::IO::Socket for operations. This
|
||||
should be already installed.
|
||||
|
||||
|
||||
5. How to debug?
|
||||
You can switch on debugging with the constructor ( see below ).
|
||||
|
||||
|
||||
6. How to use it?
|
||||
|
||||
|
||||
$sam = new Net::I2P::SAM('127.0.0.1','7656',1);
|
||||
|
||||
# you can omit host/port - then the defult is assumed
|
||||
# the 3rd argument is the debugging switch. If you switched it on
|
||||
# there'll be a default debug in /tmp/sam-debug
|
||||
# $sam will now either contain a object reference or 0
|
||||
# if it's 0 then we coudl not talk to the SAM bridge at all ( connect failed)
|
||||
# or we could not agree to a version
|
||||
|
||||
# next we can tune the tunnel settings we want for this session:
|
||||
# The syntax is just like the one used on www.i2p.net/sam
|
||||
|
||||
$sam->change_settings("inbound.length=1,inbound.lenghVariance=0,outbound.length=1,outbound.lengthVariance=0,inbound.nickname=fun,outbound.nickname=fun");
|
||||
|
||||
# next we open a new session.
|
||||
# only stream is supported
|
||||
# most of the time we use a transient destination
|
||||
# otherwise state the hosts.txt name you want to use as in your session
|
||||
# direction is most of the times both :)
|
||||
# this cab return 1 for success or 0 for failure
|
||||
$sam->create_session("STREAM","TRANSIENT","BOTH");
|
||||
|
||||
|
||||
# now connect to our service
|
||||
$sam->connect($sam->lookup("myservice.i2p"));
|
||||
|
||||
# or
|
||||
|
||||
$sam->connect("I2PDESTINATIONKEY.....AAAA");
|
||||
|
||||
# if this returns 1 - we got a stream session and can now receive and send data
|
||||
# otherwise consult the debug.
|
||||
|
||||
|
||||
|
||||
# Send data is just like the the raw perl send
|
||||
# we send the data as scalar var and optional flags (most of the times 0)
|
||||
$sam->send($data,0);
|
||||
|
||||
|
||||
# receiving data is similar to the perl recv
|
||||
# we define the mac number of bytes and optional flags
|
||||
|
||||
$indata = $sam->receive(512,0);
|
||||
|
||||
# close the session
|
||||
|
||||
$sam->close();
|
||||
|
||||
# that's the most important things to know.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
922
apps/sam/perl/SAM.pm
Normal file
922
apps/sam/perl/SAM.pm
Normal file
@@ -0,0 +1,922 @@
|
||||
# Perl Basic SAM module
|
||||
# created 2005 by postman (postman@i2pmail.org)
|
||||
# This code is under GPL.
|
||||
# This code is proof of concept
|
||||
|
||||
|
||||
package Net::I2P::SAM;
|
||||
require 5.001;
|
||||
use strict;
|
||||
use POSIX;
|
||||
|
||||
|
||||
# we do not extend the IO::Socket:INET Package
|
||||
# we just use it, so we keep our stuff sorted
|
||||
# This is a testsetup - the next release but
|
||||
# be an extension of IO::Socket::INET;
|
||||
|
||||
use IO::Socket::INET;
|
||||
|
||||
|
||||
use vars qw($VERSION @ISA @EXPORT);
|
||||
|
||||
|
||||
sub new {
|
||||
my ($this,$host,$port,$debug) = @_;
|
||||
my $classname=ref($this) || $this;
|
||||
my $self = {};
|
||||
bless($self,$classname);
|
||||
|
||||
|
||||
#%{$self->{conf}} = The hash where we store tunnel / SAM Settings
|
||||
#$self->{debug} = Whether we should output debugging lines ( this is very helpful when having problems)
|
||||
#$self->{debugfile} = Where to log
|
||||
#%{$self->{samreply}} = This hash stores the key=VALUE pairs of sam reply
|
||||
#$self->{sock} = The INET Socket over which we talk to the SAM bridge
|
||||
#$self->{inbuffer} = a simple layouted inbuffer
|
||||
#$self->{outbuffer} = a simple layouted outbuffer
|
||||
# ( the buffers are for dealing with differences between user wanted in/outputsizes
|
||||
# and what we're able to deliver on a machine side)
|
||||
#$self->{sessiontype} = unused ( will be used when we support different sessiontypes )
|
||||
#$self->{lookupresult}= contains the result of a SAM host/userhost naming lookup;
|
||||
#$self->{samerror} = The human readable SAM error message ( if wanted )
|
||||
#$self->{streamid} = The virtual streamid. It will be created upon connect;
|
||||
#$self->{bytestoread} = contains the reported size of a packet from sam
|
||||
#$self->{bytestosend} = contains the wanted size of a packet to sam
|
||||
#$self->{hasbanner} = is set to 1 when we receive a greeting string upon connect
|
||||
|
||||
#
|
||||
|
||||
%{$self->{conf}}=();
|
||||
if($debug==1) {
|
||||
$self->{debug}=1;
|
||||
}
|
||||
$self->{debugfile}="/tmp/sam-debug";
|
||||
$self->{debughandle}=undef;
|
||||
%{$self->{samreply}}=undef;
|
||||
$self->{sock}=undef;
|
||||
$self->{inbuffer}=undef;
|
||||
$self->{outbuffer}=undef;
|
||||
$self->{sessiontype}=undef;
|
||||
$self->{lookupresult}=undef;
|
||||
$self->{samerror}=undef;
|
||||
$self->{streamid}=undef;
|
||||
$self->{bytestoread}=undef;
|
||||
$self->{bytestosend}=undef;
|
||||
$self->{hasbanner}=1;
|
||||
|
||||
# state == -1 (no socket exists)
|
||||
# state == 0 (socket exists, but we're not helloed)
|
||||
# state == 1 (socket exists, and we're helloed)
|
||||
# state == 200 ( we bound a session)
|
||||
# state == 250 ( we have a virtual stream)
|
||||
|
||||
$self->{state}=-1;
|
||||
|
||||
|
||||
# if the user has specified a host/port for contacting the SAM
|
||||
# Bridge, we'll override the defaults. Otherwise we just use
|
||||
# defaults.
|
||||
#
|
||||
if($host) {
|
||||
${$self->{conf}}{host}=$host;
|
||||
} else {
|
||||
${$self->{conf}}{host}="127.0.0.1";
|
||||
}
|
||||
|
||||
if($port) {
|
||||
${$self->{conf}}{port}=$port;
|
||||
} else {
|
||||
${$self->{conf}}{port}=7656;
|
||||
}
|
||||
# defaults for the tunnelparameters
|
||||
# see www.i2p.net/sam
|
||||
${$self->{conf}}{iblength}=2;
|
||||
${$self->{conf}}{oblength}=2;
|
||||
${$self->{conf}}{ibquant}=2;
|
||||
${$self->{conf}}{obquant}=2;
|
||||
${$self->{conf}}{ibbackup}=0;
|
||||
${$self->{conf}}{obbackup}=0;
|
||||
${$self->{conf}}{ibvariance}=0;
|
||||
${$self->{conf}}{obvariance}=0;
|
||||
${$self->{conf}}{iballowzero}="true";
|
||||
${$self->{conf}}{oballowzero}="true";
|
||||
${$self->{conf}}{ibduration}=600000;
|
||||
${$self->{conf}}{obduration}=600000;
|
||||
${$self->{conf}}{ibnickname}="SAM-Perl-Destination";
|
||||
${$self->{conf}}{obnickname}="SAM-Perl-Destination";
|
||||
|
||||
|
||||
|
||||
# ok, let's open a simple debug file
|
||||
# if the user wants us
|
||||
if($self->{debug} == 1) {
|
||||
if ( ! open (LOGFILE,">" .$self->{debugfile})) {
|
||||
print "constructor: Cannot open debugging file, switching debugging off.";
|
||||
$self->{debug}=0;
|
||||
}
|
||||
|
||||
# switch off the nerveracking buffer for the debugfile
|
||||
select((select(LOGFILE), $| = 1)[0]);
|
||||
}
|
||||
|
||||
# ok now, lets move to the manager
|
||||
# he manages connecting and hello
|
||||
# if the proc_mgr returns 1 the user will get our
|
||||
# object reference. if not, he'll just get a 0.
|
||||
if($self->proc_mgr()) {
|
||||
return $self;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub proc_mgr {
|
||||
my ($self) = @_;
|
||||
my $return=undef;
|
||||
|
||||
if ($self->{state} == -1) {
|
||||
$self->log("Debug: SAM::proc_mgr(): Opening Socket Connection to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
$return=$self->sam_connect();
|
||||
if($return==0) {
|
||||
return 0;
|
||||
}
|
||||
if($return == 1) {
|
||||
$self->log("Debug: SAM::proc_mgr(): State Transition -1 => 0");
|
||||
$self->{state}=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($self->{state}==0) {
|
||||
if(!$self->hello()) {
|
||||
$self->log("Debug: SAM::proc_mgr(): Closing Socket");
|
||||
$self->{sock}->close();
|
||||
$self->log("State SAM::proc_mgr(): Transition 0 => -1");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub change_settings {
|
||||
my ($self,$list)=@_;
|
||||
my (@tochange,$id,$v,$k);
|
||||
|
||||
# we cannot change the settings if we have a session already
|
||||
# so our state must be 1
|
||||
|
||||
if($self->{state} >1) {
|
||||
$self->log("Debug: SAM::change_settings(): Cannot change tunnel settings after establishing a session.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@tochange=split(",",$list);
|
||||
foreach $id (@tochange) {
|
||||
($k,$v)=split("=",$id);
|
||||
lc($v);
|
||||
lc($k);
|
||||
|
||||
|
||||
$self->log("Debug: SAM::change_settings(): Parsed Setting: Key: $k - Value: $v");
|
||||
if($k eq "inbound.length" || $k eq "outbound.length") {
|
||||
# make v an int
|
||||
$v*1;
|
||||
if ($v >3 || $v < 0) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{iballowzero} eq "false" && $k eq "inbound.length" && $v==0) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{oballowzero} eq "false" && $k eq "outbound.length" && $v==0) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if($k eq "inbound.length") {
|
||||
${$self->{conf}}{iblength}=$v;
|
||||
}
|
||||
if($k eq "outbound.length") {
|
||||
${$self->{conf}}{oblength}=$v;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if($k eq "inbound.quantity" || $k eq "outbound.quantity") {
|
||||
$v*1;
|
||||
if($v < 0 || $v >3 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.quantity") {
|
||||
${$self->{conf}}{ibquant}=$v;
|
||||
}
|
||||
if($k eq "outbound.quantity") {
|
||||
${$self->{conf}}{obquant}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.backupquantity" || $k eq "outbound.backupquantity") {
|
||||
$v*1;
|
||||
if($v < 0 || $v >2 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.backupquantity") {
|
||||
${$self->{conf}}{ibbackup}=$v;
|
||||
}
|
||||
if($k eq "outbound.backupquantity") {
|
||||
${$self->{conf}}{obbackup}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.lengthvariance" || $k eq "outbound.lengthvariance") {
|
||||
$v*1;
|
||||
if($v < -2 || $v >2 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.lengthvariance") {
|
||||
${$self->{conf}}{ibvariance}=$v;
|
||||
}
|
||||
if($k eq "outbound.lengthvariance") {
|
||||
${$self->{conf}}{ibvariance}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.duration" || $k eq "outbound.duration") {
|
||||
$v*1;
|
||||
if($v < 300000 || $v >1200000 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.duration") {
|
||||
${$self->{conf}}{ibduration}=$v;
|
||||
}
|
||||
if($k eq "outbound.duration") {
|
||||
${$self->{conf}}{obduration}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.nickname" || $k eq "outbound.nickname") {
|
||||
$v=substr($v,0,20);
|
||||
|
||||
if ($k eq "inbound.nickname") {
|
||||
${$self->{conf}}{ibnickname}=$v;
|
||||
}
|
||||
if($k eq "outbound.nickname") {
|
||||
${$self->{conf}}{obnickname}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.allowzerohop" || $k eq "outbound.allowzerohop") {
|
||||
if($v ne "true" && $v ne "false") {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (must be boolean)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{iblength} ==0 && $k eq "inbound.allowzerohop" && $v eq "false") {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{oblength} == 0 && $k eq "outbound.allowzerohop" && $v eq "false") {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ($k eq "inbound.allowzerohop") {
|
||||
${$self->{conf}}{iballowzero}=$v;
|
||||
}
|
||||
if($k eq "outbound.allowzerohop") {
|
||||
${$self->{conf}}{oballowzero}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
$self->log("Debug: SAM::change_settings(): Setting $k to $v");
|
||||
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub hello {
|
||||
my ($self) = @_;
|
||||
my $greeting="HELLO VERSION MIN=1.0 MAX=1.0\n";
|
||||
my $return=undef;
|
||||
my $return2=undef;
|
||||
|
||||
$self->{outbuffer} .= $greeting;
|
||||
$return=$self->raw_send();
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("HELLO")) {
|
||||
$self->{state}=1;
|
||||
$self->log("Debug: SAM::hello(): State Transition 0 => 1");
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::hello(): HELLO Failed. Cannot read HELLO response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error: SAM::hello(): HELLO Failed. Cannot send HELLO String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub create_session {
|
||||
my ($self,$type,$destination,$direction) = @_;
|
||||
my $line="SESSION CREATE ";
|
||||
my $return;
|
||||
|
||||
uc($type);
|
||||
# WE ARE ONLY DOING STREAMS ATM
|
||||
if ($type ne "STREAM") {
|
||||
$self->log("Error: SAM::create_session(): SESSION failed. Only session of STREAM type are allowed");
|
||||
return 0;
|
||||
}
|
||||
if(length($destination)==0) {
|
||||
$self->log("Warn: SAM::create_session(): SESSION: fallback setting on destination to TRANSIENT.");
|
||||
$destination="TRANSIENT";
|
||||
}
|
||||
|
||||
$line.="STYLE=$type DESTINATION=$destination";
|
||||
|
||||
uc($direction);
|
||||
if($direction ne "BOTH" && $direction ne "CREATE" && $direction ne "RECEIVE") {
|
||||
$self->log("Warn: SAM::create_session(): SESSION: fallback setting on direction to BOTH.");
|
||||
$direction="BOTH";
|
||||
}
|
||||
|
||||
$line .= " DIRECTION=$direction";
|
||||
$line .= " inbound.length=".${$self->{conf}}{iblength}." outbound.length=".${$self->{conf}}{oblength};
|
||||
$line .= " inbound.quantity=".${$self->{conf}}{ibquant}." outbound.quantity=".${$self->{conf}}{obquant};
|
||||
$line .= " inbound.backupQuantity=".${$self->{conf}}{ibbackup}." outbound.backupQuantity=".${$self->{conf}}{obbackup};
|
||||
$line .= " inbound.lengthVariance=".${$self->{conf}}{ibvariance}." outbound.lengthVariance=".${$self->{conf}}{obvariance};
|
||||
$line .= " inbound.duration=".${$self->{conf}}{ibduration}." outbound.duration=".${$self->{conf}}{obduration};
|
||||
$line .= " inbound.nickname=".${$self->{conf}}{ibnickname}." outbound.nickname=".${$self->{conf}}{obnickname};
|
||||
$line .= " inbound.allowZeroHop=".${$self->{conf}}{iballowzero}." outbound.allowZeroHop=".${$self->{conf}}{oballowzero};
|
||||
$line .="\n";
|
||||
|
||||
$self->{outbuffer}.=$line;
|
||||
$return=$self->raw_send();
|
||||
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("SESSION ")) {
|
||||
$self->{state}=200;
|
||||
$self->log("Debug: SAM::create_session(): State Transition 1 => 200");
|
||||
# flush the whole inbuffer;
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::create_session(): SESSION Failed. Cannot read SAM Response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error: SAM::create_session(): SESSION Failed. Cannot send SESSION String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sub parse_sam {
|
||||
|
||||
# this is the main function that parses all SAM replies
|
||||
# depending on wanted action and state we'll set different
|
||||
# properties like $self->{bytestoread} etc.
|
||||
# parse_sam does not CUT OUT SAM messages from the payloads
|
||||
# (look at $self->recv for that)
|
||||
#
|
||||
my ($self,$state) = @_;
|
||||
my (@data,$id,$k,$v);
|
||||
%{$self->{samreply}}=();
|
||||
|
||||
|
||||
uc($state);
|
||||
|
||||
if( $self->{inbuffer} =~ /^(.[^ ]*) (.[^ ]*) (.*)$/m ) {
|
||||
${$self->{samreply}}{COMMAND}=$1;
|
||||
${$self->{samreply}}{REPLY}=$2;
|
||||
|
||||
@data=split(" ",$3);
|
||||
|
||||
foreach $id (@data) {
|
||||
($k,$v)=split("=",$id);
|
||||
|
||||
#print "k: $k - v: $v\n";
|
||||
${$self->{samreply}}{$k}=$v;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Could not parse the SAM Reply. Has the specs changed?");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if($state eq "HELLO") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "HELLO") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state HELLO but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "REPLY") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): Got a OK result for HELLO");
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error :SAM::parse_sam(): Got no OK Result for HELLO");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Unknown Reply type for HELLO dialogue");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($state eq "SESSION") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "SESSION") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state SESSION but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "STATUS") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): Got a OK result for SESSION");
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Got no OK Result for SESSION: ".${$self->{samreply}}{RESULT});
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Unknown Reply type for SESSION dialogue");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($state eq "NAMING") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "NAMING") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state NAMING but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "REPLY") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): Got a OK result for NAMING");
|
||||
$self->{lookupresult}=${$self->{samreply}}{VALUE};
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Got no OK Result for NAMING: ".${$self->{samreply}}{RESULT});
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Unknown Reply type for NAMING dialogue");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($state eq "STREAM") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "STREAM") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state STREAM but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
# CREATING STREAMS
|
||||
if(${$self->{samreply}}{REPLY} eq "STATUS") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): STREAM STATUS OK - Next action is awaited");
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): STREAM STATUS NOT OK.: ".${$self->{samreply}}{RESULT});
|
||||
|
||||
if(length(${$self->{samreply}}{MESSAGE}) == 0) {
|
||||
$self->{samerror}=${$self->{samreply}}{RESULT};
|
||||
} else {
|
||||
$self->{samerror}=${$self->{samreply}}{MESSAGE};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# SEND/RECEIVING STREAMS
|
||||
# this can happen directly after a connect
|
||||
if(${$self->{samreply}}{REPLY} eq "RECEIVED") {
|
||||
if(${$self->{samreply}}{SIZE} > 0) {
|
||||
$self->log("Debug: SAM::parse_sam(): SAM notify: RECEIVED Data. SIZE=".${$self->{samreply}}{SIZE});
|
||||
$self->{bytestoread}=${$self->{samreply}}{SIZE};
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Received empty payload");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# STREAMS are closed - bad thing
|
||||
# this can happen directly after a connect
|
||||
if(${$self->{samreply}}{REPLY} eq "CLOSED") {
|
||||
$self->log("Error: SAM::parse_sam(): Stream is closed: We need to interupt the session");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub raw_send {
|
||||
|
||||
# this function sends a crafted SAM request + payload to the
|
||||
# SAM socket
|
||||
my ($self) = @_;
|
||||
my $return;
|
||||
|
||||
$self->log("Debug: SAM::raw_send(): >>> ".$self->{outbuffer});
|
||||
$return = $self->{sock}->send($self->{outbuffer},0);
|
||||
if(! defined($return)) {
|
||||
$self->log("Error: SAM::raw_send(): Cannot send to Socket");
|
||||
$self->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( $return == length($self->{outbuffer}) || $!{EWOULDBLOCK} ) {
|
||||
substr($self->{outbuffer},0, $return) = '';
|
||||
delete $self->{outbuffer} unless length($self->{outbuffer});
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error :SAM::raw_send(): Could send, but length does not match. Closing");
|
||||
$self->close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub raw_read {
|
||||
my ($self,$size) = @_;
|
||||
my $input;
|
||||
my $data;
|
||||
|
||||
# this reads SAM replies from the SAM socket and fills the
|
||||
# inbuffer
|
||||
|
||||
if(!$size || $size > POSIX::BUFSIZ) {
|
||||
$size=POSIX::BUFSIZ;
|
||||
}
|
||||
|
||||
|
||||
$input = $self->{sock}->recv($data, $size, 0);
|
||||
|
||||
if(defined($input) && length($data) >= 1) {
|
||||
|
||||
|
||||
$self->log("Debug: SAM::raw_read(): <<< $data");
|
||||
if(length($self->{inbuffer}) == 0 ) {
|
||||
$self->{inbuffer} = $data;
|
||||
} else {
|
||||
$self->{inbuffer} .= $data;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
if ( $!{EAGAIN} ) {
|
||||
$self->{bytestoread}=0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
sub sam_connect {
|
||||
# bsic connect to the sam bridge socket itself
|
||||
my ($self)=@_;
|
||||
my $return=undef;
|
||||
|
||||
|
||||
|
||||
$return=$self->{sock}=IO::Socket::INET->new(${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
|
||||
if($return==0) {
|
||||
$self->log("Debug: SAM::sam_connect(): Connection failed to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
return 0;
|
||||
} else {
|
||||
$self->log("Debug: SAM::sam_connect(): Connection established to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
sub send {
|
||||
|
||||
# the public fumction to send data to sam
|
||||
# the user gives his payload and we create
|
||||
# valid SAM requests + payload from it ( as much as needed)
|
||||
my ($self,$content,$flags)=@_;
|
||||
my $return;
|
||||
my $maxsize=(POSIX::BUFSIZ-100);
|
||||
|
||||
if($self->{state}!=250) {
|
||||
$self->log("Error: SAM::send(): wrong state for send command: Needed:250 Recent:".$self->{state});
|
||||
return 0;
|
||||
}
|
||||
# ok, what can happen?
|
||||
# it could be that $content is far bigger than
|
||||
# POSIX::BUFSIZ; so we need to do a little loop
|
||||
# apart from that sending is in our hand
|
||||
|
||||
if(length($content) > $maxsize) {
|
||||
$self->{outbuffer}.="STREAM SEND ID=".$self->{streamid}." SIZE=$maxsize\n".substr($content,0,$maxsize);
|
||||
$content=substr($content,$maxsize,length($content));
|
||||
} else {
|
||||
$self->{outbuffer}.="STREAM SEND ID=".$self->{streamid}." SIZE=".length($content)."\n".$content;
|
||||
}
|
||||
|
||||
if( $self->raw_send()) {
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::send(): Could not send. Closing Link");
|
||||
}
|
||||
}
|
||||
|
||||
sub recv {
|
||||
my($self,$varname,$size,$flags)=@_;
|
||||
my $return;
|
||||
my $tunebuffer;
|
||||
my $counter;
|
||||
my $chunk;
|
||||
|
||||
|
||||
# main recv wrapper. We need to invest a few thoughts
|
||||
# for this. To the user it will be like a $socket->recv
|
||||
# (hopefully)
|
||||
# at first some sanity checks
|
||||
if(!$flags) {
|
||||
$flags=0;
|
||||
}
|
||||
|
||||
$self->{bytestoread}=0;
|
||||
# size must not exceed The posix limit;
|
||||
|
||||
if(!$size || $size > POSIX::BUFSIZ) {
|
||||
$self->log("Warn: SAM::recv(): Setting buffersize to POSIX::BUFSIZ");
|
||||
$size=POSIX::BUFSIZ;
|
||||
}
|
||||
|
||||
|
||||
# nobody should call is prior to state 250
|
||||
if($self->{state}!=250) {
|
||||
$self->log("Error: SAM::recv(): wrong state for rcv command: Needed:250 Recent:".$self->{state});
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
# we have a greeting banner left from connect
|
||||
# flush it to the user. This can happen on several services
|
||||
# like smtp/pop3/nntp but not on HTTP and other stuff
|
||||
|
||||
if($self->{hasbanner}) {
|
||||
|
||||
#print "D: ".$self->{inbuffer};
|
||||
|
||||
if(length($self->{inbuffer}) >0 ) {
|
||||
$chunk=substr($self->{inbuffer},0, $size);
|
||||
$self->{hasbanner}=0;
|
||||
substr($self->{inbuffer},0, $size)='';
|
||||
return $chunk;
|
||||
} else {
|
||||
$self->log("Error: SAM::recv(): Should have a banner but i have empty inbuffer?");
|
||||
return 0;
|
||||
}
|
||||
# should never reach here
|
||||
return 1;
|
||||
}
|
||||
# when there's something in the inbuffer left
|
||||
# flush it to the user. If the amount of data is bigger than
|
||||
# the userspecified limit, only transfer $size Bytes to the
|
||||
# client
|
||||
if(length($self->{inbuffer}) > 0) {
|
||||
$chunk=substr($self->{inbuffer},0, $size);
|
||||
substr($self->{inbuffer},0, $size)='';
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
# OK, we got noting in the inbuffer
|
||||
# we'll fetch a new chunk of data and then add the data to the inbuffer
|
||||
# if bytestoread is bigger than POSIX::BUFSIZ we'll internally use
|
||||
# a loop of reads and fill the buffer til bytestoread is 0
|
||||
|
||||
if(length($self->{inbuffer}) == 0) {
|
||||
# read the first packet
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("STREAM") && ${$self->{samreply}}{REPLY} eq "RECEIVED") {
|
||||
# it's possible that the packet does not contain any payload at all!!
|
||||
# if this is the case we need
|
||||
if($self->{inbuffer}=~/^.*$/) {
|
||||
$self->log("Warn: SAM::recv(): Got only only one line from SAM - but i expected some payload too. Calling raw_read again ");
|
||||
$self->raw_read();
|
||||
}
|
||||
# ok, cut the SAM HEADER from the payload
|
||||
$self->{inbuffer}=substr($self->{inbuffer},(length($self->{inbuffer})-$self->{bytestoread}), length($self->{inbuffer}));
|
||||
$self->log("Debug: SAM::recv(): Recived a Stream Packet and cut the SAM header out");
|
||||
# ok, check if bytestoread is still bigger than our buffersize
|
||||
# this means we can load more. Dangerous loop but well...
|
||||
|
||||
while(length($self->{inbuffer}) < $self->{bytestoread}) {
|
||||
# this should definately end some day
|
||||
$counter++;
|
||||
if($counter > 10000) {
|
||||
$self->log("Error: SAM::recv(): WTF, could not fill inbuffer as predicted by SAM header");
|
||||
last;
|
||||
}
|
||||
# read as long til we have read all of the payload provided by SAM
|
||||
|
||||
$self->log("Debug: SAM::recv(): Load another chunk. Buffersize:".length($self->{inbuffer})." / Bytestoread:".$self->{bytestoread} );
|
||||
$self->raw_read();
|
||||
}
|
||||
|
||||
} else {
|
||||
$self->log("Error: SAM::recv(): parse_sam() did not succeed. Interupting");
|
||||
delete $self->{inbuffer};
|
||||
return 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
$self->log("Error: SAM::recv(): Could not read from the socket");
|
||||
delete $self->{inbuffer};
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
$chunk=substr($self->{inbuffer},0, $size);
|
||||
substr($self->{inbuffer},0, $size)='';
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
sub lookup {
|
||||
my($self,$name)=@_;
|
||||
my $return;
|
||||
|
||||
$self->{outbuffer}="NAMING LOOKUP NAME=$name\n";
|
||||
$return=$self->raw_send();
|
||||
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("NAMING")) {
|
||||
$self->log("Debug: SAM::lookup(): Naming Lookup successful");
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return $self->{lookupresult};
|
||||
}
|
||||
} else {
|
||||
$self->log("Error :SAM::lookup(): NAMING Failed. Cannot read SAM Response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error :SAM::lookup(): NAMING Failed. Cannot send NAMING String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub sam_close {
|
||||
my ($self) =@_;
|
||||
$self->log("Debug: SAM::sam_close(): Closing Socket to SAM");
|
||||
$self->{sock}->close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub close {
|
||||
my ($self)=@_;
|
||||
my $return;
|
||||
$self->{outbuffer}.="STREAM CLOSE ID=".$self->{streamid}."\n";
|
||||
$self->log("Debug: SAM::close(): CLosing Stream with id: ".$self->{streamid});
|
||||
$return=$self->raw_send();
|
||||
# well, we do not care wether this worked or not
|
||||
$self->sam_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub connect {
|
||||
my ($self,$destination)=@_;
|
||||
my $return;
|
||||
|
||||
$self->{streamid}= int(rand(200000000));
|
||||
if(length($destination) == 0) {
|
||||
$self->log("Error: SAM::connect(): need I2P destination to connect to with the SAM Bridge");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$self->{outbuffer}.="STREAM CONNECT ID=".$self->{streamid}." DESTINATION=$destination\n";
|
||||
$return=$self->raw_send();
|
||||
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("STREAM")) {
|
||||
if(${$self->{samreply}}{REPLY} eq "STATUS") {
|
||||
$self->{state}=250;
|
||||
$self->log("Debug: SAM::connect(): State Transition 200 => 250");
|
||||
# flush the whole inbuffer;
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "RECEIVED") {
|
||||
$self->{state}=250;
|
||||
$self->log("Debug: SAM::connect(): State Transition 200 => 250. Got a banner");
|
||||
delete $self->{inbuffer};
|
||||
|
||||
#print "D: toread:".$self->{bytestoread};
|
||||
#print "D: buffer:".$self->{inbuffer}."\n";
|
||||
#print "D: buffersize:".length($self->{inbuffer})."\n";
|
||||
|
||||
$self->raw_read();
|
||||
#print "D: toread:".$self->{bytestoread};
|
||||
#print "D: buffer:".$self->{inbuffer}."\n";
|
||||
#print "D: buffersize:".length($self->{inbuffer})."\n";
|
||||
|
||||
$self->{inbuffer}=substr($self->{inbuffer}, 0, $self->{bytestoread});
|
||||
$self->{hasbanner}=1;
|
||||
|
||||
#print "D: toread:".$self->{bytestoread};
|
||||
#print "D: buffer:".$self->{inbuffer}."\n";
|
||||
#print "D: buffersize:".length($self->{inbuffer})."\n";
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::connect(): STREAM Failed. Cannot read SAM Response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error: SAM::connect(): STREAM Failed. Cannot send SESSION String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub log {
|
||||
my($self,$line)=@_;
|
||||
if($line=~/.*\n$/) {
|
||||
chomp $line;
|
||||
}
|
||||
if ( $self->{debug} ) {
|
||||
print LOGFILE "$line\n";
|
||||
}
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use Net::SAM::RawSession;
|
||||
use Net::SAM::DatagramSession;
|
||||
|
||||
$sam=Net::SAM::DatagramSession->new($ARGV[0], "BOTH", "tunnels.depthInbound=0");
|
||||
print "Connected? " . $sam->connected() . "\n";
|
||||
|
||||
$me = $sam->lookup("ME");
|
||||
print "Sending to $me.\n";
|
||||
$sam->send($me,"fooquux");
|
||||
|
||||
$sam->readprocess();
|
||||
($source, $message) = @{ $sam->receive() };
|
||||
print "$source -- $message";
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ public class Connection {
|
||||
private Log _log;
|
||||
private ConnectionManager _connectionManager;
|
||||
private Destination _remotePeer;
|
||||
private byte _sendStreamId[];
|
||||
private byte _receiveStreamId[];
|
||||
private long _sendStreamId;
|
||||
private long _receiveStreamId;
|
||||
private long _lastSendTime;
|
||||
private long _lastSendId;
|
||||
private boolean _resetReceived;
|
||||
@@ -72,8 +72,8 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 5*1000;
|
||||
public static final long MIN_RESEND_DELAY = 3*1000;
|
||||
public static final long MAX_RESEND_DELAY = 10*1000;
|
||||
public static final long MIN_RESEND_DELAY = 2*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
public static int DISCONNECT_TIMEOUT = 5*60*1000;
|
||||
@@ -102,7 +102,7 @@ public class Connection {
|
||||
_closeSentOn = -1;
|
||||
_closeReceivedOn = -1;
|
||||
_unackedPacketsReceived = 0;
|
||||
_congestionWindowEnd = 0;
|
||||
_congestionWindowEnd = _options.getWindowSize()-1;
|
||||
_highestAckedThrough = -1;
|
||||
_lastCongestionSeenAt = MAX_WINDOW_SIZE*2; // lets allow it to grow
|
||||
_lastCongestionTime = -1;
|
||||
@@ -153,8 +153,12 @@ public class Connection {
|
||||
synchronized (_outboundPackets) {
|
||||
if (!started)
|
||||
_context.statManager().addRateData("stream.chokeSizeBegin", _outboundPackets.size(), timeoutMs);
|
||||
if (!_connected)
|
||||
return false;
|
||||
|
||||
// no need to wait until the other side has ACKed us before sending the first few wsize
|
||||
// packets through
|
||||
// if (!_connected)
|
||||
// return false;
|
||||
|
||||
started = true;
|
||||
if ( (_outboundPackets.size() >= _options.getWindowSize()) || (_activeResends > 0) ||
|
||||
(_lastSendId - _highestAckedThrough > _options.getWindowSize()) ) {
|
||||
@@ -189,7 +193,28 @@ public class Connection {
|
||||
}
|
||||
|
||||
void ackImmediately() {
|
||||
PacketLocal packet = _receiver.send(null, 0, 0);
|
||||
PacketLocal packet = null;
|
||||
synchronized (_outboundPackets) {
|
||||
if (_outboundPackets.size() > 0) {
|
||||
// ordered, so pick the lowest to retransmit
|
||||
Iterator iter = _outboundPackets.values().iterator();
|
||||
packet = (PacketLocal)iter.next();
|
||||
//iter.remove();
|
||||
}
|
||||
}
|
||||
if (packet != null) {
|
||||
ResendPacketEvent evt = (ResendPacketEvent)packet.getResendEvent();
|
||||
if (evt != null) {
|
||||
boolean sent = evt.retransmit(false);
|
||||
if (sent) {
|
||||
return;
|
||||
} else {
|
||||
//SimpleTimer.getInstance().addEvent(evt, evt.getNextSendTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we don't have anything to retransmit, send a small ack
|
||||
packet = _receiver.send(null, 0, 0);
|
||||
//packet.releasePayload();
|
||||
}
|
||||
|
||||
@@ -205,7 +230,7 @@ public class Connection {
|
||||
_resetSent = true;
|
||||
if (_resetSentOn <= 0)
|
||||
_resetSentOn = _context.clock().now();
|
||||
if ( (_remotePeer == null) || (_sendStreamId == null) ) return;
|
||||
if ( (_remotePeer == null) || (_sendStreamId <= 0) ) return;
|
||||
PacketLocal reply = new PacketLocal(_context, _remotePeer);
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
@@ -256,20 +281,24 @@ public class Connection {
|
||||
remaining = 0;
|
||||
if (packet.isFlagSet(Packet.FLAG_CLOSE) || (remaining < 2)) {
|
||||
packet.setOptionalDelay(0);
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
} else {
|
||||
int delay = _options.getRTT() / 2;
|
||||
int delay = _options.getRTO() / 2;
|
||||
packet.setOptionalDelay(delay);
|
||||
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
|
||||
if (delay > 0)
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
|
||||
}
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
|
||||
long timeout = _options.getRTT() + MIN_RESEND_DELAY;
|
||||
long timeout = _options.getRTO();
|
||||
if (timeout > MAX_RESEND_DELAY)
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resend in " + timeout + " for " + packet, new Exception("Sent by"));
|
||||
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
|
||||
RetransmissionTimer.getInstance().addEvent(new ResendPacketEvent(packet, timeout + _context.clock().now()), timeout);
|
||||
}
|
||||
|
||||
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
||||
@@ -307,16 +336,20 @@ public class Connection {
|
||||
}
|
||||
|
||||
List ackPackets(long ackThrough, long nacks[]) {
|
||||
if (nacks == null) {
|
||||
_highestAckedThrough = ackThrough;
|
||||
if (ackThrough < _highestAckedThrough) {
|
||||
// dupack which won't tell us anything
|
||||
} else {
|
||||
long lowest = -1;
|
||||
for (int i = 0; i < nacks.length; i++) {
|
||||
if ( (lowest < 0) || (nacks[i] < lowest) )
|
||||
lowest = nacks[i];
|
||||
if (nacks == null) {
|
||||
_highestAckedThrough = ackThrough;
|
||||
} else {
|
||||
long lowest = -1;
|
||||
for (int i = 0; i < nacks.length; i++) {
|
||||
if ( (lowest < 0) || (nacks[i] < lowest) )
|
||||
lowest = nacks[i];
|
||||
}
|
||||
if (lowest - 1 > _highestAckedThrough)
|
||||
_highestAckedThrough = lowest - 1;
|
||||
}
|
||||
if (lowest - 1 > _highestAckedThrough)
|
||||
_highestAckedThrough = lowest - 1;
|
||||
}
|
||||
|
||||
List acked = null;
|
||||
@@ -463,7 +496,9 @@ public class Connection {
|
||||
_receiver.destroy();
|
||||
if (_activityTimer != null)
|
||||
SimpleTimer.getInstance().removeEvent(_activityTimer);
|
||||
_activityTimer = null;
|
||||
//_activityTimer = null;
|
||||
if (_inputStream != null)
|
||||
_inputStream.streamErrorOccurred(new IOException("disconnected!"));
|
||||
|
||||
if (_disconnectScheduledOn < 0) {
|
||||
_disconnectScheduledOn = _context.clock().now();
|
||||
@@ -514,17 +549,30 @@ public class Connection {
|
||||
synchronized (_connectLock) { _connectLock.notifyAll(); }
|
||||
}
|
||||
|
||||
private boolean _remotePeerSet = false;
|
||||
/** who are we talking with */
|
||||
public Destination getRemotePeer() { return _remotePeer; }
|
||||
public void setRemotePeer(Destination peer) { _remotePeer = peer; }
|
||||
public void setRemotePeer(Destination peer) {
|
||||
if (_remotePeerSet) throw new RuntimeException("Remote peer already set [" + _remotePeer + ", " + peer + "]");
|
||||
_remotePeerSet = true;
|
||||
_remotePeer = peer;
|
||||
}
|
||||
|
||||
private boolean _sendStreamIdSet = false;
|
||||
/** what stream do we send data to the peer on? */
|
||||
public byte[] getSendStreamId() { return _sendStreamId; }
|
||||
public void setSendStreamId(byte[] id) { _sendStreamId = id; }
|
||||
public long getSendStreamId() { return _sendStreamId; }
|
||||
public void setSendStreamId(long id) {
|
||||
if (_sendStreamIdSet) throw new RuntimeException("Send stream ID already set [" + _sendStreamId + ", " + id + "]");
|
||||
_sendStreamIdSet = true;
|
||||
_sendStreamId = id;
|
||||
}
|
||||
|
||||
private boolean _receiveStreamIdSet = false;
|
||||
/** stream the peer sends data to us on. (may be null) */
|
||||
public byte[] getReceiveStreamId() { return _receiveStreamId; }
|
||||
public void setReceiveStreamId(byte[] id) {
|
||||
public long getReceiveStreamId() { return _receiveStreamId; }
|
||||
public void setReceiveStreamId(long id) {
|
||||
if (_receiveStreamIdSet) throw new RuntimeException("Receive stream ID already set [" + _receiveStreamId + ", " + id + "]");
|
||||
_receiveStreamIdSet = true;
|
||||
_receiveStreamId = id;
|
||||
synchronized (_connectLock) { _connectLock.notifyAll(); }
|
||||
}
|
||||
@@ -651,7 +699,7 @@ public class Connection {
|
||||
void waitForConnect() {
|
||||
long expiration = _context.clock().now() + _options.getConnectTimeout();
|
||||
while (true) {
|
||||
if (_connected && (_receiveStreamId != null) && (_sendStreamId != null) ) {
|
||||
if (_connected && (_receiveStreamId > 0) && (_sendStreamId > 0) ) {
|
||||
// w00t
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("waitForConnect(): Connected and we have stream IDs");
|
||||
@@ -695,27 +743,56 @@ public class Connection {
|
||||
}
|
||||
|
||||
private void resetActivityTimer() {
|
||||
if (_options.getInactivityTimeout() <= 0) return;
|
||||
if (_activityTimer == null) return;
|
||||
long howLong = _activityTimer.getTimeLeft();
|
||||
if (_options.getInactivityTimeout() <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer, but its gone!", new Exception("where did it go?"));
|
||||
return;
|
||||
}
|
||||
if (_activityTimer == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer, but its gone!", new Exception("where did it go?"));
|
||||
return;
|
||||
}
|
||||
long howLong = _options.getInactivityTimeout();
|
||||
howLong += _context.random().nextInt(30*1000); // randomize it a bit, so both sides don't do it at once
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer to " + howLong);
|
||||
_log.debug("Resetting the inactivity timer to " + howLong, new Exception("Reset by"));
|
||||
// this will get rescheduled, and rescheduled, and rescheduled...
|
||||
SimpleTimer.getInstance().addEvent(_activityTimer, howLong);
|
||||
RetransmissionTimer.getInstance().removeEvent(_activityTimer);
|
||||
RetransmissionTimer.getInstance().addEvent(_activityTimer, howLong);
|
||||
}
|
||||
|
||||
private class ActivityTimer implements SimpleTimer.TimedEvent {
|
||||
public void timeReached() {
|
||||
// uh, nothing more to do...
|
||||
if (!_connected) return;
|
||||
if (!_connected) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but we are already closed");
|
||||
return;
|
||||
}
|
||||
// we got rescheduled already
|
||||
if (getTimeLeft() > 0) return;
|
||||
long left = getTimeLeft();
|
||||
if (left > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is time left (" + left + ")");
|
||||
RetransmissionTimer.getInstance().addEvent(ActivityTimer.this, left);
|
||||
return;
|
||||
}
|
||||
// these are either going to time out or cause further rescheduling
|
||||
if (getUnackedPacketsSent() > 0) return;
|
||||
if (getUnackedPacketsSent() > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there are unacked packets");
|
||||
return;
|
||||
}
|
||||
// wtf, this shouldn't have been scheduled
|
||||
if (_options.getInactivityTimeout() <= 0) return;
|
||||
if (_options.getInactivityTimeout() <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is no timer...");
|
||||
return;
|
||||
}
|
||||
// if one of us can't talk...
|
||||
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) return;
|
||||
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but we are closing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, with action=" + _options.getInactivityAction());
|
||||
|
||||
// bugger it, might as well do the hard work now
|
||||
switch (_options.getInactivityAction()) {
|
||||
@@ -741,7 +818,9 @@ public class Connection {
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
disconnect(true);
|
||||
_inputStream.streamErrorOccurred(new IOException("Inactivity timeout"));
|
||||
_outputStream.streamErrorOccurred(new IOException("Inactivity timeout"));
|
||||
disconnect(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -762,18 +841,19 @@ public class Connection {
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[Connection ");
|
||||
if (_receiveStreamId != null)
|
||||
buf.append(Base64.encode(_receiveStreamId));
|
||||
if (_receiveStreamId > 0)
|
||||
buf.append(Packet.toId(_receiveStreamId));
|
||||
else
|
||||
buf.append("unknown");
|
||||
buf.append("<-->");
|
||||
if (_sendStreamId != null)
|
||||
buf.append(Base64.encode(_sendStreamId));
|
||||
if (_sendStreamId > 0)
|
||||
buf.append(Packet.toId(_sendStreamId));
|
||||
else
|
||||
buf.append("unknown");
|
||||
buf.append(" wsize: ").append(_options.getWindowSize());
|
||||
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
|
||||
buf.append(" rtt: ").append(_options.getRTT());
|
||||
buf.append(" rto: ").append(_options.getRTO());
|
||||
// not synchronized to avoid some kooky races
|
||||
buf.append(" unacked outbound: ").append(_outboundPackets.size()).append(" ");
|
||||
/*
|
||||
@@ -840,18 +920,29 @@ public class Connection {
|
||||
*/
|
||||
private class ResendPacketEvent implements SimpleTimer.TimedEvent {
|
||||
private PacketLocal _packet;
|
||||
public ResendPacketEvent(PacketLocal packet) {
|
||||
private long _nextSendTime;
|
||||
public ResendPacketEvent(PacketLocal packet, long sendTime) {
|
||||
_packet = packet;
|
||||
_nextSendTime = sendTime;
|
||||
packet.setResendPacketEvent(ResendPacketEvent.this);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
public long getNextSendTime() { return _nextSendTime; }
|
||||
public void timeReached() { retransmit(true); }
|
||||
/**
|
||||
* Retransmit the packet if we need to.
|
||||
*
|
||||
* @param penalize true if this retransmission is caused by a timeout, false if we
|
||||
* are just sending this packet instead of an ACK
|
||||
* @return true if the packet was sent, false if it was not
|
||||
*/
|
||||
public boolean retransmit(boolean penalize) {
|
||||
if (_packet.getAckTime() > 0)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (_resetSent || _resetReceived) {
|
||||
_packet.cancelled();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -872,20 +963,26 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Delaying resend of " + _packet + " as there are "
|
||||
+ _activeResends + " active resends already in play");
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
|
||||
return;
|
||||
RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
|
||||
_nextSendTime = 1000 + _context.clock().now();
|
||||
return false;
|
||||
}
|
||||
// revamp various fields, in case we need to ack more, etc
|
||||
_inputStream.updateAcks(_packet);
|
||||
_packet.setOptionalDelay(getOptions().getChoke());
|
||||
int choke = getOptions().getChoke();
|
||||
_packet.setOptionalDelay(choke);
|
||||
if (choke > 0)
|
||||
_packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
_packet.setOptionalMaxSize(getOptions().getMaxMessageSize());
|
||||
_packet.setResendDelay(getOptions().getResendDelay());
|
||||
_packet.setReceiveStreamId(_receiveStreamId);
|
||||
_packet.setSendStreamId(_sendStreamId);
|
||||
if (_packet.getReceiveStreamId() <= 0)
|
||||
_packet.setReceiveStreamId(_receiveStreamId);
|
||||
if (_packet.getSendStreamId() <= 0)
|
||||
_packet.setSendStreamId(_sendStreamId);
|
||||
|
||||
int newWindowSize = getOptions().getWindowSize();
|
||||
|
||||
if (_ackSinceCongestion) {
|
||||
if (penalize && _ackSinceCongestion) {
|
||||
// only shrink the window once per window
|
||||
if (_packet.getSequenceNum() > _lastCongestionHighestUnacked) {
|
||||
congestionOccurred();
|
||||
@@ -895,7 +992,7 @@ public class Connection {
|
||||
newWindowSize = 1;
|
||||
|
||||
// setRTT has its own ceiling
|
||||
getOptions().setRTT(getOptions().getRTT() + 10*1000);
|
||||
//getOptions().setRTT(getOptions().getRTT() + 10*1000);
|
||||
getOptions().setWindowSize(newWindowSize);
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -940,7 +1037,7 @@ public class Connection {
|
||||
synchronized (_outboundPackets) {
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numSends - 1 > _options.getMaxResends()) {
|
||||
@@ -950,20 +1047,23 @@ public class Connection {
|
||||
disconnect(false);
|
||||
} else {
|
||||
//long timeout = _options.getResendDelay() << numSends;
|
||||
long rtt = _options.getRTT();
|
||||
if (rtt < MIN_RESEND_DELAY)
|
||||
rtt = MIN_RESEND_DELAY;
|
||||
long timeout = rtt << (numSends-1);
|
||||
long rto = _options.getRTO();
|
||||
if (rto < MIN_RESEND_DELAY)
|
||||
rto = MIN_RESEND_DELAY;
|
||||
long timeout = rto << (numSends-1);
|
||||
if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) )
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
||||
RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
||||
_nextSendTime = timeout + _context.clock().now();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Packet acked before resend (resend="+ resend + "): "
|
||||
// + _packet + " on " + Connection.this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -142,15 +143,18 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
data.setValid(size);
|
||||
data.setOffset(0);
|
||||
packet.setPayload(data);
|
||||
if ( (ackOnly && !forceIncrement) && (!isFirst) )
|
||||
packet.setSequenceNum(0);
|
||||
if ( (ackOnly && !forceIncrement) && (!isFirst) )
|
||||
packet.setSequenceNum(0);
|
||||
else
|
||||
packet.setSequenceNum(con.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(con.getSendStreamId());
|
||||
packet.setReceiveStreamId(con.getReceiveStreamId());
|
||||
|
||||
con.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(con.getOptions().getChoke());
|
||||
int choke = con.getOptions().getChoke();
|
||||
packet.setOptionalDelay(choke);
|
||||
if (choke > 0)
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
packet.setResendDelay(con.getOptions().getResendDelay());
|
||||
|
||||
if (con.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
@@ -166,6 +170,9 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
packet.setOptionalFrom(con.getSession().getMyDestination());
|
||||
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
|
||||
}
|
||||
if (DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) {
|
||||
packet.setFlag(Packet.FLAG_NO_ACK);
|
||||
}
|
||||
|
||||
// don't set the closed flag if this is a plain ACK and there are outstanding
|
||||
// packets sent, otherwise the other side could receive the CLOSE prematurely,
|
||||
|
||||
@@ -50,7 +50,7 @@ class ConnectionHandler {
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
|
||||
SimpleTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
|
||||
RetransmissionTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
|
||||
synchronized (_synQueue) {
|
||||
_synQueue.add(packet);
|
||||
_synQueue.notifyAll();
|
||||
@@ -127,7 +127,7 @@ class ConnectionHandler {
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setAckThrough(packet.getSequenceNum());
|
||||
reply.setSendStreamId(packet.getReceiveStreamId());
|
||||
reply.setReceiveStreamId(null);
|
||||
reply.setReceiveStreamId(0);
|
||||
reply.setOptionalFrom(_manager.getSession().getMyDestination());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending RST: " + reply + " because of " + packet);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user