beginning of branch i2p.i2p.i2p
This commit is contained in:
278
apps/httptunnel/doc/COPYING
Normal file
278
apps/httptunnel/doc/COPYING
Normal file
@ -0,0 +1,278 @@
|
||||
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.
|
11
apps/httptunnel/doc/readme.license.txt
Normal file
11
apps/httptunnel/doc/readme.license.txt
Normal file
@ -0,0 +1,11 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/httptunnel module is the root of the
|
||||
HTTPTunnel application, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/httptunnel module is released
|
||||
under the GPL plus the java exception unless
|
||||
otherwise marked. Alternate licenses that may be
|
||||
used include BSD, Cryptix, and MIT, as well as
|
||||
code granted into the public domain.
|
47
apps/httptunnel/java/build.xml
Normal file
47
apps/httptunnel/java/build.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="httptunnel">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/httptunnel.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.httptunnel.HTTPTunnel" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="HTTPTunnel" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
@ -0,0 +1,67 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Listens on a port for HTTP connections.
|
||||
*/
|
||||
public class HTTPListener extends Thread {
|
||||
|
||||
private static final Log _log = new Log(HTTPListener.class);
|
||||
|
||||
private int port;
|
||||
private String listenHost;
|
||||
private SocketManagerProducer smp;
|
||||
|
||||
public HTTPListener(SocketManagerProducer smp, int port,
|
||||
String listenHost) {
|
||||
this.smp = smp;
|
||||
this.port = port;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
InetAddress lh = listenHost == null
|
||||
? null
|
||||
: InetAddress.getByName(listenHost);
|
||||
ServerSocket ss = new ServerSocket(port, 0, lh);
|
||||
while(true) {
|
||||
Socket s = ss.accept();
|
||||
new HTTPSocketHandler(this, s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while accepting connections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proxyUsed=false;
|
||||
|
||||
public boolean firstProxyUse() {
|
||||
// FIXME: check a config option here
|
||||
if (true) return false;
|
||||
if (proxyUsed) {
|
||||
return false;
|
||||
} else {
|
||||
proxyUsed=true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public SocketManagerProducer getSMP() {
|
||||
return smp;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public void handleNotImplemented(OutputStream out) throws IOException {
|
||||
out.write(("HTTP/1.1 200 Document following\n\n"+
|
||||
"<h1>Feature not implemented</h1>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.httptunnel.handler.RootHandler;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handles a single HTTP socket connection.
|
||||
*/
|
||||
public class HTTPSocketHandler extends Thread {
|
||||
|
||||
private static final Log _log = new Log(HTTPSocketHandler.class);
|
||||
|
||||
private Socket s;
|
||||
private HTTPListener httpl;
|
||||
private RootHandler h;
|
||||
|
||||
public HTTPSocketHandler(HTTPListener httpl, Socket s) {
|
||||
this.httpl = httpl;
|
||||
this.s=s;
|
||||
h = RootHandler.getInstance();
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(s.getInputStream());
|
||||
out = new BufferedOutputStream(s.getOutputStream());
|
||||
Request req = new Request(in);
|
||||
h.handle(req, httpl, out);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling data", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("IOException in finalizer", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
apps/httptunnel/java/src/net/i2p/httptunnel/HTTPTunnel.java
Normal file
110
apps/httptunnel/java/src/net/i2p/httptunnel/HTTPTunnel.java
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* HTTPTunnel
|
||||
* (c) 2003 - 2004 mihi
|
||||
*
|
||||
* 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; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* In addition, as a special exception, mihi gives permission to link
|
||||
* the code of this program with the proprietary Java implementation
|
||||
* provided by Sun (or other vendors as well), and distribute linked
|
||||
* combinations including the two. You must obey the GNU General
|
||||
* Public License in all respects for all of the code used other than
|
||||
* the proprietary Java implementation. If you modify this file, you
|
||||
* may extend this exception to your version of the file, but you are
|
||||
* not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
|
||||
/**
|
||||
* HTTPTunnel main class.
|
||||
*/
|
||||
public class HTTPTunnel {
|
||||
|
||||
/**
|
||||
* Create a HTTPTunnel instance.
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param mcDonaldsMode whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers,
|
||||
boolean mcDonaldsMode, int listenPort) {
|
||||
this(initialManagers, maxManagers, mcDonaldsMode, listenPort,
|
||||
"127.0.0.1", 7654);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HTTPTunnel instance.
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param mcDonaldsMode whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
* @param i2cpAddress the I2CP address
|
||||
* @param i2cpPort the I2CP port
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers,
|
||||
boolean mcDonaldsMode, int listenPort,
|
||||
String i2cpAddress, int i2cpPort) {
|
||||
SocketManagerProducer smp =
|
||||
new SocketManagerProducer(initialManagers, maxManagers,
|
||||
mcDonaldsMode, i2cpAddress, i2cpPort);
|
||||
new HTTPListener(smp, listenPort, "127.0.0.1");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String host = "127.0.0.1";
|
||||
int port = 7654, max = 1;
|
||||
boolean mc = false;
|
||||
if (args.length >1) {
|
||||
if (args.length == 4) {
|
||||
host = args[2];
|
||||
port = Integer.parseInt(args[3]);
|
||||
} else if (args.length != 2) {
|
||||
showInfo(); return;
|
||||
}
|
||||
max = Integer.parseInt(args[1]);
|
||||
} else if (args.length != 1) {
|
||||
showInfo(); return;
|
||||
}
|
||||
if (max == 0) {
|
||||
max = 1;
|
||||
} else if (max <0) {
|
||||
max = -max;
|
||||
mc = true;
|
||||
}
|
||||
new HTTPTunnel(null, max, mc, Integer.parseInt(args[0]), host, port);
|
||||
}
|
||||
|
||||
private static void showInfo() {
|
||||
System.out.println
|
||||
("Usage: java HTTPTunnel <listenPort> [<max> "+
|
||||
"[<i2cphost> <i2cpport>]]\n"+
|
||||
" <listenPort> port to listen for browsers\n"+
|
||||
" <max> max number of SocketMangers in pool, "+
|
||||
"use neg. number\n"+
|
||||
" to use each SocketManager only once "+
|
||||
"(default: 1)\n"+
|
||||
" <i2cphost> host to connect to the router "+
|
||||
"(default: 127.0.0.1)\n"+
|
||||
" <i2cpport> port to connect to the router "+
|
||||
"(default: 7654)");
|
||||
}
|
||||
}
|
130
apps/httptunnel/java/src/net/i2p/httptunnel/Request.java
Normal file
130
apps/httptunnel/java/src/net/i2p/httptunnel/Request.java
Normal file
@ -0,0 +1,130 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A HTTP request (GET or POST). This will be passed to a hander for
|
||||
* handling it.
|
||||
*/
|
||||
public class Request {
|
||||
|
||||
private static final Log _log = new Log(Request.class);
|
||||
|
||||
// all strings are forced to be ISO-8859-1 encoding
|
||||
private String method;
|
||||
private String url;
|
||||
private String proto;
|
||||
private String params;
|
||||
private String postData;
|
||||
|
||||
public Request(InputStream in) throws IOException {
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader(in, "ISO-8859-1"));
|
||||
String line = br.readLine();
|
||||
if (line == null) { // no data at all
|
||||
method = null;
|
||||
_log.error("Connection but no data");
|
||||
return;
|
||||
}
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
method = line;
|
||||
url="";
|
||||
_log.error("Malformed HTTP request: "+line);
|
||||
} else {
|
||||
method = line.substring(0,pos);
|
||||
url=line.substring(pos+1);
|
||||
}
|
||||
proto="";
|
||||
pos = url.indexOf(" ");
|
||||
if (pos != -1) {
|
||||
proto=url.substring(pos); // leading space intended
|
||||
url = url.substring(0,pos);
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(512);
|
||||
while((line=br.readLine()) != null) {
|
||||
if (line.length() == 0) break;
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
params = sb.toString(); // no leading empty line!
|
||||
sb = new StringBuffer();
|
||||
// hack for POST requests, ripped from HttpClient
|
||||
// this won't work for large POSTDATA
|
||||
// FIXME: do this better, please.
|
||||
if (!method.equals("GET")) {
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i=br.read();
|
||||
if (i != -1) {
|
||||
sb.append((char)i);
|
||||
}
|
||||
}
|
||||
postData = sb.toString();
|
||||
} else {
|
||||
postData="";
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] toByteArray() throws IOException {
|
||||
if (method == null) return null;
|
||||
return toISO8859_1String().getBytes("ISO-8859-1");
|
||||
|
||||
}
|
||||
|
||||
private String toISO8859_1String() throws IOException {
|
||||
if (method == null) return null;
|
||||
return method+" "+url+proto+"\r\n"+params+"\r\n"+postData;
|
||||
}
|
||||
|
||||
public String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setURL(String newURL) {
|
||||
url=newURL;
|
||||
}
|
||||
|
||||
public String getParam(String name) {
|
||||
try {
|
||||
BufferedReader br= new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
return line.substring(name.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setParam(String name, String value) {
|
||||
try {
|
||||
StringBuffer sb = new StringBuffer(params.length()+value.length());
|
||||
BufferedReader br= new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
boolean replaced = false;
|
||||
while((line=br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
replaced=true;
|
||||
if (value == null) continue; // kill param
|
||||
line = name+value;
|
||||
}
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
if (!replaced && value != null) {
|
||||
sb.append(name).append(value).append("\r\n");
|
||||
}
|
||||
params=sb.toString();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
|
||||
/**
|
||||
* Produces SocketManagers in a thread and gives them to those who
|
||||
* need them.
|
||||
*/
|
||||
public class SocketManagerProducer extends Thread {
|
||||
|
||||
private ArrayList myManagers = new ArrayList();
|
||||
private HashMap usedManagers = new HashMap();
|
||||
private int port;
|
||||
private String host;
|
||||
private int maxManagers;
|
||||
private boolean mcDonalds;
|
||||
|
||||
public SocketManagerProducer(I2PSocketManager[] initialManagers,
|
||||
int maxManagers,
|
||||
boolean mcDonaldsMode,
|
||||
String host, int port) {
|
||||
if (maxManagers < 1) {
|
||||
throw new IllegalArgumentException("maxManagers < 1");
|
||||
}
|
||||
this.host=host;
|
||||
this.port=port;
|
||||
mcDonalds=mcDonaldsMode;
|
||||
if (initialManagers != null) {
|
||||
myManagers.addAll(Arrays.asList(initialManagers));
|
||||
}
|
||||
this.maxManagers=maxManagers;
|
||||
mcDonalds=mcDonaldsMode;
|
||||
setDaemon(true);
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thread producing new SocketManagers.
|
||||
*/
|
||||
public void run() {
|
||||
while (true) {
|
||||
synchronized(this) {
|
||||
// without mcDonalds mode, we most probably need no
|
||||
// new managers.
|
||||
while (!mcDonalds && myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
}
|
||||
// produce a new manager, regardless whether it is needed
|
||||
// or not. Do not synchronized this part, since it can be
|
||||
// quite time-consuming.
|
||||
I2PSocketManager newManager =
|
||||
I2PSocketManagerFactory.createManager(host, port,
|
||||
new Properties());
|
||||
// when done, check if it is needed.
|
||||
synchronized(this) {
|
||||
while(myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
myManagers.add(newManager);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a manager for connecting to a given destination. Each
|
||||
* destination will always get the same manager.
|
||||
*
|
||||
* @param dest the destination to connect to
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager(String dest) {
|
||||
I2PSocketManager result = (I2PSocketManager) usedManagers.get(dest);
|
||||
if (result == null) {
|
||||
result = getManager();
|
||||
usedManagers.put(dest,result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a "new" SocketManager. Depending on the anonymity settings,
|
||||
* this can be a completely new one or one randomly selected from
|
||||
* a pool.
|
||||
*
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager() {
|
||||
while (myManagers.size() == 0) {
|
||||
myWait(); // no manager here, so wait until one is produced
|
||||
}
|
||||
int which = (int)(Math.random()*myManagers.size());
|
||||
I2PSocketManager result = (I2PSocketManager) myManagers.get(which);
|
||||
if (mcDonalds) {
|
||||
myManagers.remove(which);
|
||||
notifyAll();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void myWait() {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Chain multiple filters. Decorator pattern...
|
||||
*/
|
||||
public class ChainFilter implements Filter {
|
||||
|
||||
private static final Log _log = new Log(ChainFilter.class);
|
||||
|
||||
public Collection filters;
|
||||
|
||||
public ChainFilter(Collection filters) {
|
||||
this.filters=filters;
|
||||
}
|
||||
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
byte[] buf = toFilter;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
buf = f.filter(buf);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
public byte[] finish() {
|
||||
// this is a bit complicated. Think about it...
|
||||
try {
|
||||
byte[] buf = EMPTY;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (buf.length != 0) {
|
||||
baos.write(f.filter(buf));
|
||||
}
|
||||
baos.write(f.finish());
|
||||
buf = baos.toByteArray();
|
||||
}
|
||||
return buf;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error chaining filters", ex);
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
/**
|
||||
* A generic filtering interface.
|
||||
*/
|
||||
public interface Filter {
|
||||
|
||||
/**
|
||||
* An empty byte array.
|
||||
*/
|
||||
public static final byte[] EMPTY = new byte[0];
|
||||
|
||||
/**
|
||||
* Filter some data. Not all filtered data need to be returned.
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter);
|
||||
|
||||
/**
|
||||
* Data stream has finished. Return all of the rest data.
|
||||
*/
|
||||
public byte[] finish();
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
/**
|
||||
* A filter letting everything pass as is.
|
||||
*/
|
||||
public class NullFilter implements Filter {
|
||||
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
return toFilter;
|
||||
}
|
||||
|
||||
public byte[] finish() {
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for browsing Eepsites.
|
||||
*/
|
||||
public class EepHandler {
|
||||
|
||||
private static final Log _log = new Log(EepHandler.class);
|
||||
|
||||
protected ErrorHandler errorHandler;
|
||||
|
||||
/* package private */ EepHandler(ErrorHandler eh) {
|
||||
errorHandler=eh;
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
boolean fromProxy, String destination)
|
||||
throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = NamingService.getInstance().lookup(destination);
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"Could not lookup host: "+destination);
|
||||
return;
|
||||
}
|
||||
I2PSocketManager sm = smp.getManager(destination);
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
req.setParam("Host: ", dest.toBase64());
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handle(Request req, Filter f, OutputStream out,
|
||||
Destination dest, I2PSocketManager sm)
|
||||
throws IOException {
|
||||
I2PSocket s = null;
|
||||
boolean written = false;
|
||||
try {
|
||||
synchronized(sm) {
|
||||
s = sm.connect(dest, new I2PSocketOptions());
|
||||
}
|
||||
InputStream in = new BufferedInputStream(s.getInputStream());
|
||||
OutputStream sout = new BufferedOutputStream(s.getOutputStream());
|
||||
sout.write(req.toByteArray());
|
||||
sout.flush();
|
||||
byte[] buffer = new byte[16384], filtered;
|
||||
int len;
|
||||
while ((len=in.read(buffer)) != -1) {
|
||||
if (len != buffer.length) {
|
||||
byte[] b2 = new byte[len];
|
||||
System.arraycopy(buffer, 0, b2, 0, len);
|
||||
filtered=f.filter(b2);
|
||||
} else {
|
||||
filtered=f.filter(buffer);
|
||||
}
|
||||
written=true;
|
||||
out.write(filtered);
|
||||
}
|
||||
filtered=f.finish();
|
||||
written=true;
|
||||
out.write(filtered);
|
||||
out.flush();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} finally {
|
||||
if (s != null) s.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for general error messages.
|
||||
*/
|
||||
public class ErrorHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class);
|
||||
|
||||
/* package private */ ErrorHandler() {
|
||||
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl,
|
||||
OutputStream out, String error) throws IOException {
|
||||
// FIXME: Make nicer messages for more likely errors.
|
||||
out.write(("HTTP/1.1 500 Internal Server Error\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(("<html><head><title>"+error+"</title></head><body><h1>"+
|
||||
error+"</h1>An internal error occurred while "+
|
||||
"handling a request by HTTPTunnel:<br><b>"+error+
|
||||
"</b><h2>Complete request:</h2><b>---</b><br><i><pre>\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(req.toByteArray());
|
||||
out.write(("</pre></i><br><b>---</b></body></html>")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for requests that do not require any connection to anyone
|
||||
* (except errors).
|
||||
*/
|
||||
public class LocalHandler {
|
||||
|
||||
private static final Log _log = new Log(LocalHandler.class);
|
||||
|
||||
/* package private */ LocalHandler() {
|
||||
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
boolean fromProxy) throws IOException {
|
||||
//FIXME: separate multiple pages, not only a start page
|
||||
//FIXME: provide some info on this page
|
||||
out.write(("HTTP/1.1 200 Document following\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n\r\n"+
|
||||
"<html><head><title>Welcome to I2P HTTPTunnel</title>"+
|
||||
"</head><body><h1>Welcome to I2P HTTPTunnel</h1>You can "+
|
||||
"browse Eepsites by adding an eepsite name to the request."+
|
||||
"</body></html>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void handleProxyConfWarning(Request req, HTTPListener httpl,
|
||||
OutputStream out) throws IOException {
|
||||
//FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
|
||||
}
|
||||
|
||||
public void handleHTTPWarning(Request req, HTTPListener httpl,
|
||||
OutputStream out, boolean fromProxy)
|
||||
throws IOException {
|
||||
// FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for proxying "normal" HTTP requests.
|
||||
*/
|
||||
public class ProxyHandler extends EepHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class);
|
||||
|
||||
/* package private */ ProxyHandler(ErrorHandler eh) {
|
||||
super(eh);
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
boolean fromProxy) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = findProxy();
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"Could not find proxy");
|
||||
return;
|
||||
}
|
||||
// one manager for all proxy requests
|
||||
I2PSocketManager sm = smp.getManager("--proxy--");
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
private Destination findProxy() {
|
||||
//FIXME!
|
||||
return NamingService.getInstance().lookup("squid.i2p");
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main handler for all requests. Dispatches requests to other handlers.
|
||||
*/
|
||||
public class RootHandler {
|
||||
|
||||
private static final Log _log = new Log(RootHandler.class);
|
||||
|
||||
private RootHandler() {
|
||||
errorHandler=new ErrorHandler();
|
||||
localHandler=new LocalHandler();
|
||||
proxyHandler=new ProxyHandler(errorHandler);
|
||||
eepHandler=new EepHandler(errorHandler);
|
||||
}
|
||||
|
||||
private ErrorHandler errorHandler;
|
||||
private ProxyHandler proxyHandler;
|
||||
private LocalHandler localHandler;
|
||||
private EepHandler eepHandler;
|
||||
|
||||
private static RootHandler instance;
|
||||
|
||||
public static synchronized RootHandler getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new RootHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl,
|
||||
OutputStream out) throws IOException {
|
||||
String url=req.getURL();
|
||||
System.out.println(url);
|
||||
boolean byProxy = false;
|
||||
int pos;
|
||||
if (url.startsWith("http://")) { // access via proxy
|
||||
byProxy=true;
|
||||
if (httpl.firstProxyUse()) {
|
||||
localHandler.handleProxyConfWarning(req,httpl,out);
|
||||
return;
|
||||
}
|
||||
url = url.substring(7);
|
||||
pos = url.indexOf("/");
|
||||
String host;
|
||||
String rest;
|
||||
if (pos == -1) {
|
||||
errorHandler.handle(req, httpl, out, "No host end in URL");
|
||||
return;
|
||||
} else {
|
||||
host = url.substring(0,pos);
|
||||
url = url.substring(pos);
|
||||
if ("i2p".equals(host) || "i2p.i2p".equals(host)) {
|
||||
// normal request; go on below...
|
||||
} else if (host.endsWith(".i2p")) {
|
||||
// "old" service request, send a redirect...
|
||||
out.write(("HTTP/1.1 302 Moved\r\nLocation: "+
|
||||
"http://i2p.i2p/"+host+url+
|
||||
"\r\n\r\n").getBytes("ISO-8859-1"));
|
||||
return;
|
||||
} else {
|
||||
// this is for proxying to the real web
|
||||
proxyHandler.handle(req, httpl, out, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (url.equals("/")) { // main page
|
||||
url="/_/local/index";
|
||||
} else if (!url.startsWith("/")) {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"No leading slash in URL: "+url);
|
||||
return;
|
||||
}
|
||||
String dest;
|
||||
url=url.substring(1);
|
||||
pos = url.indexOf("/");
|
||||
if (pos == -1) {
|
||||
dest=url;
|
||||
url="/";
|
||||
} else {
|
||||
dest = url.substring(0,pos);
|
||||
url=url.substring(pos);
|
||||
}
|
||||
req.setURL(url);
|
||||
if (dest.equals("_")) { // no eepsite
|
||||
if (url.startsWith("/local/")) { // local request
|
||||
req.setURL(url.substring(6));
|
||||
localHandler.handle(req, httpl, out, byProxy);
|
||||
} else if (url.startsWith("/http/")) { // http warning
|
||||
localHandler.handleHTTPWarning(req, httpl, out, byProxy);
|
||||
} else if (url.startsWith("/proxy/")) { // http proxying
|
||||
req.setURL("http://"+url.substring(7));
|
||||
proxyHandler.handle(req, httpl, out, byProxy);
|
||||
} else {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"No local handler for this URL: "+url);
|
||||
}
|
||||
} else {
|
||||
eepHandler.handle(req, httpl, out, byProxy, dest);
|
||||
}
|
||||
}
|
||||
}
|
278
apps/i2ptunnel/doc/COPYING
Normal file
278
apps/i2ptunnel/doc/COPYING
Normal file
@ -0,0 +1,278 @@
|
||||
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.
|
11
apps/i2ptunnel/doc/readme.license.txt
Normal file
11
apps/i2ptunnel/doc/readme.license.txt
Normal file
@ -0,0 +1,11 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/i2ptunnel module is the root of the
|
||||
I2PTunnel application, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/i2ptunnel module is released
|
||||
under the GPL plus the java exception unless
|
||||
otherwise marked. Alternate licenses that may be
|
||||
used include BSD, Cryptix, and MIT, as well as
|
||||
code granted into the public domain.
|
21
apps/i2ptunnel/doc/readme.txt
Normal file
21
apps/i2ptunnel/doc/readme.txt
Normal file
@ -0,0 +1,21 @@
|
||||
I2PTunnel allows to tunnel a usual TCP connection over I2P.
|
||||
|
||||
A server needs to have a public key; a client connects to that.
|
||||
|
||||
Starting a client:
|
||||
|
||||
tunnel <localport> <pubkey>
|
||||
localport: the port where you want to connect to
|
||||
pubkey: the public key of the server
|
||||
|
||||
|
||||
Starting a server:
|
||||
|
||||
tunnel <remotehost> <remoteport> <privkey>
|
||||
remotehost: the host to connect to when a connection comes.
|
||||
remoteport: the port to connect to when a connection comes.
|
||||
privkey: your private (secret) key
|
||||
|
||||
To stop a client/server, hit <Return>
|
||||
|
||||
Have fun!
|
47
apps/i2ptunnel/java/build.xml
Normal file
47
apps/i2ptunnel/java/build.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2ptunnel">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/i2ptunnel.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.i2ptunnel.I2PTunnel" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2PTunnel" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
61
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java
Normal file
61
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java
Normal file
@ -0,0 +1,61 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Read what i2ptunnel logs, and expose it in a buffer
|
||||
*
|
||||
*/
|
||||
class BufferLogger implements Logging {
|
||||
private final static Log _log = new Log(BufferLogger.class);
|
||||
private ByteArrayOutputStream _baos;
|
||||
private boolean _ignore;
|
||||
|
||||
public BufferLogger() {
|
||||
_baos = new ByteArrayOutputStream(512);
|
||||
_ignore = false;
|
||||
}
|
||||
|
||||
private final static String EMPTY = "";
|
||||
public String getBuffer() {
|
||||
if (_ignore) return EMPTY;
|
||||
else return new String(_baos.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't care about anything else the logger receives. This is useful
|
||||
* for loggers passed in to servers and clients, since they will continue
|
||||
* to add info to the logger, but if we're instantiated by the tunnel manager,
|
||||
* its likely we only care about the first few messages it sends us.
|
||||
*
|
||||
*/
|
||||
public void ignoreFurtherActions() {
|
||||
_ignore = true;
|
||||
synchronized (_baos) {
|
||||
_baos.reset();
|
||||
}
|
||||
_baos = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in some random data
|
||||
*/
|
||||
public void log(String s) {
|
||||
if (_ignore) return;
|
||||
if (s != null) {
|
||||
_log.debug("logging [" + s + "]");
|
||||
try {
|
||||
_baos.write(s.getBytes());
|
||||
_baos.write('\n');
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error logging [" + s + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
998
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
Normal file
998
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
Normal file
@ -0,0 +1,998 @@
|
||||
/*
|
||||
* I2PTunnel
|
||||
* (c) 2003 - 2004 mihi
|
||||
*
|
||||
* 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; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* In addition, as a special exception, mihi gives permission to link
|
||||
* the code of this program with the proprietary Java implementation
|
||||
* provided by Sun (or other vendors as well), and distribute linked
|
||||
* combinations including the two. You must obey the GNU General
|
||||
* Public License in all respects for all of the code used other than
|
||||
* the proprietary Java implementation. If you modify this file, you
|
||||
* may extend this exception to your version of the file, but you are
|
||||
* not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.EventDispatcherImpl;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
public class I2PTunnel implements Logging, EventDispatcher {
|
||||
private final static Log _log = new Log(I2PTunnel.class);
|
||||
private final EventDispatcherImpl _event = new EventDispatcherImpl();
|
||||
|
||||
public static final int PACKET_DELAY=100;
|
||||
|
||||
public static boolean ownDest = false;
|
||||
|
||||
public static String port =
|
||||
System.getProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
public static String host = System.getProperty
|
||||
(I2PClient.PROP_TCP_HOST,"127.0.0.1");
|
||||
public static String listenHost = host;
|
||||
|
||||
private static final String nocli_args[] = {"-nocli", "-die"};
|
||||
|
||||
private List tasks=new ArrayList();
|
||||
private int next_task_id = 1;
|
||||
|
||||
private Set listeners = new HashSet();
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
new I2PTunnel(args);
|
||||
}
|
||||
|
||||
public I2PTunnel() {
|
||||
this(nocli_args);
|
||||
}
|
||||
|
||||
public I2PTunnel(String[] args) {
|
||||
this(args, null);
|
||||
}
|
||||
public I2PTunnel(String[] args, ConnectionEventListener lsnr) {
|
||||
addConnectionEventListener(lsnr);
|
||||
boolean gui=true;
|
||||
boolean checkRunByE = true;;
|
||||
boolean cli=true;
|
||||
boolean dontDie = true;
|
||||
for(int i=0;i<args.length;i++) {
|
||||
if (args[i].equals("-die")) {
|
||||
dontDie = false;
|
||||
gui=false;
|
||||
cli=false;
|
||||
checkRunByE=false;
|
||||
} else if (args[i].equals("-nogui")) {
|
||||
gui=false;
|
||||
_log.warn("The `-nogui' option of I2PTunnel is deprecated.\n"+
|
||||
"Use `-cli', `-nocli' (aka `-wait') or `-die' instead.");
|
||||
} else if (args[i].equals("-cli")) {
|
||||
gui=false;
|
||||
cli=true;
|
||||
checkRunByE=false;
|
||||
} else if (args[i].equals("-nocli") || args[i].equals("-wait")) {
|
||||
gui=false;
|
||||
cli=false;
|
||||
checkRunByE=false;
|
||||
} else if (args[i].equals("-e")) {
|
||||
runCommand(args[i+1], this);
|
||||
i++;
|
||||
if (checkRunByE) {
|
||||
checkRunByE = false;
|
||||
cli=false;
|
||||
}
|
||||
} else if (new File(args[i]).exists()) {
|
||||
runCommand("run "+args[i], this);
|
||||
} else {
|
||||
System.out.println("Unknown parameter "+args[i]);
|
||||
}
|
||||
}
|
||||
if (gui) {
|
||||
new I2PTunnelGUI(this);
|
||||
} else if (cli) {
|
||||
try {
|
||||
System.out.println("Enter 'help' for help.");
|
||||
BufferedReader r = new BufferedReader
|
||||
(new InputStreamReader(System.in));
|
||||
while(true) {
|
||||
System.out.print("I2PTunnel>");
|
||||
String cmd = r.readLine();
|
||||
if (cmd == null) break;
|
||||
runCommand(cmd, this);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
while (dontDie) {
|
||||
synchronized (this) {
|
||||
try { wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addtask (I2PTunnelTask tsk)
|
||||
{
|
||||
tsk.setTunnel(this);
|
||||
if (tsk.isOpen()) {
|
||||
tsk.setId(next_task_id);
|
||||
next_task_id++;
|
||||
synchronized (tasks) {
|
||||
tasks.add(tsk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** java 1.3 vs 1.4 :)
|
||||
*/
|
||||
private static String[] split(String src, String delim) {
|
||||
StringTokenizer tok = new StringTokenizer(src, delim);
|
||||
String vals[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < vals.length; i++)
|
||||
vals[i] = tok.nextToken();
|
||||
return vals;
|
||||
}
|
||||
public void runCommand(String cmd, Logging l) {
|
||||
if (cmd.indexOf(" ")== -1) cmd+=" ";
|
||||
int iii=cmd.indexOf(" ");
|
||||
String cmdname= cmd.substring(0,iii).toLowerCase();
|
||||
String allargs = cmd.substring(iii+1);
|
||||
String[] args = split(allargs, " "); // .split(" "); // java 1.4
|
||||
|
||||
if ("help".equals(cmdname)) {
|
||||
runHelp(l);
|
||||
} else if ("server".equals(cmdname)) {
|
||||
runServer(args, l);
|
||||
} else if ("textserver".equals(cmdname)) {
|
||||
runTextServer(args, l);
|
||||
} else if ("client".equals(cmdname)) {
|
||||
runClient(args, l);
|
||||
} else if ("httpclient".equals(cmdname)) {
|
||||
runHttpClient(args, l);
|
||||
} else if ("sockstunnel".equals(cmdname)) {
|
||||
runSOCKSTunnel(args, l);
|
||||
} else if ("config".equals(cmdname)) {
|
||||
runConfig(args, l);
|
||||
} else if ("listen_on".equals(cmdname)) {
|
||||
runListenOn(args, l);
|
||||
} else if ("genkeys".equals(cmdname)) {
|
||||
runGenKeys(args, l);
|
||||
} else if ("gentextkeys".equals(cmdname)) {
|
||||
runGenTextKeys(l);
|
||||
} else if (cmdname.equals("quit")) {
|
||||
runQuit(l);
|
||||
} else if (cmdname.equals("list")) {
|
||||
runList(l);
|
||||
} else if (cmdname.equals("close")) {
|
||||
runClose(args, l);
|
||||
} else if (cmdname.equals("run")) {
|
||||
runRun(args, l);
|
||||
} else if (cmdname.equals("lookup")) {
|
||||
runLookup(args, l);
|
||||
} else if (cmdname.equals("ping")) {
|
||||
runPing(allargs, l);
|
||||
} else if (cmdname.equals("owndest")) {
|
||||
runOwnDest(args, l);
|
||||
} else {
|
||||
l.log("Unknown command [" + cmdname + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display help information through the given logger.
|
||||
*
|
||||
* Does not fire any events to the logger
|
||||
*
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHelp(Logging l) {
|
||||
l.log("Command list:");
|
||||
l.log("config <i2phost> <i2pport>");
|
||||
l.log("listen_on <ip>");
|
||||
l.log("owndest yes|no");
|
||||
l.log("ping <args>");
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log("gentextkeys");
|
||||
l.log("client <port> <pubkey>|file:<pubkeyfile>");
|
||||
l.log("httpclient <port>");
|
||||
l.log("lookup <name>");
|
||||
l.log("quit");
|
||||
l.log("close [forced] <jobnumber>|all");
|
||||
l.log("list");
|
||||
l.log("run <commandfile>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the specified file
|
||||
*
|
||||
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
|
||||
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
|
||||
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
|
||||
*
|
||||
* @param args {hostname, portNumber, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runServer(String args[], Logging l) {
|
||||
if (args.length==3) {
|
||||
InetAddress serverHost = null;
|
||||
int portNum = -1;
|
||||
File privKeyFile = null;
|
||||
try {
|
||||
serverHost = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error("Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
portNum = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error("Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
privKeyFile = new File(args[2]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
_log.error("Private key file does not exist or is not readable: " + args[2]);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelTask task;
|
||||
task = new I2PTunnelServer(serverHost, portNum, privKeyFile,
|
||||
args[2], l, (EventDispatcher)this);
|
||||
addtask(task);
|
||||
notifyEvent("serverTaskId", new Integer(task.getId()));
|
||||
return;
|
||||
} else {
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
l.log(" creates a server that sends all incoming data\n"+
|
||||
" of its destination to host:port.");
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the given base64 stream
|
||||
*
|
||||
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
|
||||
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
|
||||
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
|
||||
*
|
||||
* @param args {hostname, portNumber, privKeyBase64}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runTextServer(String args[], Logging l) {
|
||||
if (args.length==3) {
|
||||
InetAddress serverHost = null;
|
||||
int portNum = -1;
|
||||
try {
|
||||
serverHost = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error("Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
portNum = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error("Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
task = new I2PTunnelServer(serverHost, portNum, args[2], l,
|
||||
(EventDispatcher)this);
|
||||
addtask(task);
|
||||
notifyEvent("serverTaskId", new Integer(task.getId()));
|
||||
} else {
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log(" creates a server that sends all incoming data\n"+
|
||||
" of its destination to host:port.");
|
||||
notifyEvent("textserverTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the client on the given port number pointing at the specified destination
|
||||
* (either the base64 of the destination or file:fileNameContainingDestination)
|
||||
*
|
||||
* Sets the event "clientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
|
||||
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
|
||||
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
|
||||
* Integer port number if the client is listening
|
||||
*
|
||||
* @param args {portNumber, destinationBase64 or "file:filename"}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runClient(String args[], Logging l) {
|
||||
if (args.length==2) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error("Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelTask task;
|
||||
task = new I2PTunnelClient(port, args[1], l, ownDest,
|
||||
(EventDispatcher)this);
|
||||
addtask(task);
|
||||
notifyEvent("clientTaskId", new Integer(task.getId()));
|
||||
} else {
|
||||
l.log("client <port> <pubkey>|file:<pubkeyfile>");
|
||||
l.log(" creates a client that forwards port to the pubkey.\n"+
|
||||
" use 0 as port to get a free port assigned.");
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an HTTP client on the given port number
|
||||
*
|
||||
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
|
||||
*
|
||||
* @param args {portNumber and (optionally) proxy to be used for the WWW}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHttpClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error("Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("httpclientTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
String proxy = "squid.i2p";
|
||||
if (args.length == 2) {
|
||||
proxy = args[1];
|
||||
}
|
||||
I2PTunnelTask task;
|
||||
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy,
|
||||
(EventDispatcher)this);
|
||||
addtask(task);
|
||||
notifyEvent("httpclientTaskId", new Integer(task.getId()));
|
||||
} else {
|
||||
l.log("httpclient <port> [<proxy>]");
|
||||
l.log(" creates a client that distributes HTTP requests.");
|
||||
l.log(" <proxy> (optional) indicates a proxy server to be used");
|
||||
l.log(" when trying to access an address out of the .i2p domain");
|
||||
l.log(" (the default proxy is squid.i2p).");
|
||||
notifyEvent("httpclientTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an SOCKS tunnel on the given port number
|
||||
*
|
||||
* Sets the event "sockstunnelTaskId" = Integer(taskId) after the
|
||||
* tunnel has been started (or -1 on error). Also sets
|
||||
* "openSOCKSTunnelResult" = "ok" or "error" after the client tunnel has
|
||||
* started.
|
||||
*
|
||||
* @param args {portNumber}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runSOCKSTunnel(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error("Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("sockstunnelTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher)this);
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", new Integer(task.getId()));
|
||||
} else {
|
||||
l.log("sockstunnel <port>");
|
||||
l.log(" creates a tunnel that distributes SOCKS requests.");
|
||||
notifyEvent("sockstunnelTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the i2cp host and port
|
||||
*
|
||||
* Sets the event "configResult" = "ok" or "error" after the configuration has been specified
|
||||
*
|
||||
* @param args {hostname, portNumber}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runConfig(String args[], Logging l) {
|
||||
if (args.length==2) {
|
||||
host=args[0];
|
||||
listenHost=host;
|
||||
port=args[1];
|
||||
notifyEvent("configResult", "ok");
|
||||
} else {
|
||||
l.log("config <i2phost> <i2pport>");
|
||||
l.log(" sets the connection to the i2p router.");
|
||||
notifyEvent("configResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to use its own destination for each outgoing tunnel
|
||||
*
|
||||
* Sets the event "owndestResult" = "ok" or "error" after the configuration has been specified
|
||||
*
|
||||
* @param args {yes or no}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runOwnDest(String args[], Logging l) {
|
||||
if (args.length==1 &&
|
||||
(args[0].equalsIgnoreCase("yes")
|
||||
|| args[0].equalsIgnoreCase("no"))) {
|
||||
ownDest = args[0].equalsIgnoreCase("yes");
|
||||
notifyEvent("owndestResult", "ok");
|
||||
} else {
|
||||
l.log("owndest yes|no");
|
||||
l.log(" Specifies whether to use its own destination \n"+
|
||||
" for each outgoing tunnel");
|
||||
notifyEvent("owndestResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the hostname / IP address of the interface that the tunnels should bind to
|
||||
*
|
||||
* Sets the event "listen_onResult" = "ok" or "error" after the interface has been specified
|
||||
*
|
||||
* @param args {hostname}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runListenOn(String args[], Logging l) {
|
||||
if (args.length==1) {
|
||||
listenHost=args[0];
|
||||
notifyEvent("listen_onResult", "ok");
|
||||
} else {
|
||||
l.log("listen_on <ip>");
|
||||
l.log(" sets the interface to listen for the I2PClient.");
|
||||
notifyEvent("listen_onResult", "ok");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new keypair
|
||||
*
|
||||
* Sets the event "genkeysResult" = "ok" or "error" after the generation is complete
|
||||
*
|
||||
* @param args {privateKeyFilename, publicKeyFilename} or {privateKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runGenKeys(String args[], Logging l) {
|
||||
OutputStream pubdest=null;
|
||||
if (args.length == 2) {
|
||||
try {
|
||||
pubdest=new FileOutputStream(args[1]);
|
||||
} catch (IOException ioe) {
|
||||
l.log("Error opening output stream");
|
||||
_log.error("Error generating keys to out", ioe);
|
||||
notifyEvent("genkeysResult", "error");
|
||||
return;
|
||||
}
|
||||
} else if (args.length != 1) {
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log(" creates a new keypair and prints the public key.\n"+
|
||||
" if pubkeyfile is given, saves the public key there."+
|
||||
"\n"+
|
||||
" if the privkeyfile already exists, just print/save"+
|
||||
"the pubkey.");
|
||||
notifyEvent("genkeysResult", "error");
|
||||
}
|
||||
try {
|
||||
File privKeyFile = new File(args[0]);
|
||||
if (privKeyFile.exists()) {
|
||||
l.log("File already exists.");
|
||||
showKey(new FileInputStream(privKeyFile), pubdest, l);
|
||||
} else {
|
||||
makeKey(new FileOutputStream(privKeyFile), pubdest, l);
|
||||
}
|
||||
notifyEvent("genkeysResult", "ok");
|
||||
} catch (IOException ioe) {
|
||||
l.log("Error generating keys - " + ioe.getMessage());
|
||||
notifyEvent("genkeysResult", "error");
|
||||
_log.error("Error generating keys", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new keypair
|
||||
*
|
||||
* Sets the event "privateKey" = base64 of the privateKey stream and
|
||||
* sets the event "publicDestination" = base64 of the destination
|
||||
*
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runGenTextKeys(Logging l) {
|
||||
ByteArrayOutputStream privkey = new ByteArrayOutputStream(512);
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream(512);
|
||||
makeKey(privkey, pubkey, l);
|
||||
l.log("Private key: "+Base64.encode(privkey.toByteArray()));
|
||||
notifyEvent("privateKey", Base64.encode(privkey.toByteArray()));
|
||||
notifyEvent("publicDestination", Base64.encode(pubkey.toByteArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the JVM if there are no more tasks left running. If there are tunnels
|
||||
* running, it returns.
|
||||
*
|
||||
* Sets the event "quitResult" = "error" if there are tasks running (but if there
|
||||
* aren't, well, there's no point in setting the quitResult to "ok", now is there?)
|
||||
*
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runQuit(Logging l) {
|
||||
purgetasks(l);
|
||||
synchronized (tasks) {
|
||||
if (tasks.isEmpty()) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
l.log("There are running tasks. Try 'list'.");
|
||||
notifyEvent("quitResult", "error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of currently running tasks
|
||||
*
|
||||
* Sets the event "listDone" = "done" after dumping the tasks to
|
||||
* the logger
|
||||
*
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runList(Logging l) {
|
||||
purgetasks(l);
|
||||
synchronized (tasks) {
|
||||
for (int i=0;i<tasks.size();i++) {
|
||||
I2PTunnelTask t = (I2PTunnelTask) tasks.get(i);
|
||||
l.log("[" + t.getId() + "] " + t.toString());
|
||||
}
|
||||
}
|
||||
notifyEvent("listDone", "done");
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given task (or all tasks), optionally forcing them to die a hard
|
||||
* death
|
||||
*
|
||||
* Sets the event "closeResult" = "ok" after the closing is complete
|
||||
*
|
||||
* @param args {jobNumber}, {"forced", jobNumber}, {"forced", "all"}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runClose(String args[], Logging l) {
|
||||
if (args.length == 0 || args.length > 2) {
|
||||
l.log("close [forced] <jobnumber>|all");
|
||||
l.log(" stop running tasks. either only one or all.\n"+
|
||||
" use 'forced' to also stop tasks with active connections.\n"+
|
||||
" use the 'list' command to show the job numbers");
|
||||
notifyEvent("closeResult", "error");
|
||||
} else {
|
||||
int argindex=0; // parse optional 'forced' keyword
|
||||
boolean forced=false;
|
||||
if (args[argindex].equalsIgnoreCase("forced")) {
|
||||
forced=true;
|
||||
argindex++;
|
||||
}
|
||||
if (args[argindex].equalsIgnoreCase("all")) {
|
||||
List curTasks = null;
|
||||
synchronized (tasks) {
|
||||
curTasks = new LinkedList(tasks);
|
||||
}
|
||||
|
||||
boolean error = false;
|
||||
for (int i=0;i<curTasks.size();i++) {
|
||||
I2PTunnelTask t = (I2PTunnelTask)curTasks.get(i);
|
||||
if (!closetask(t, forced, l)) {
|
||||
notifyEvent("closeResult", "error");
|
||||
error = true;
|
||||
} else if (!error) { // If there's an error, don't hide it
|
||||
notifyEvent("closeResult", "ok");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (!closetask(Integer.parseInt(args[argindex]),forced,l)){
|
||||
notifyEvent("closeResult", "error");
|
||||
} else {
|
||||
notifyEvent("closeResult", "ok");
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
l.log("Incorrect job number: " + args[argindex]);
|
||||
notifyEvent("closeResult", "error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all of the commands in the given file (one command per line)
|
||||
*
|
||||
* Sets the event "runResult" = "ok" or "error" after the closing is complete
|
||||
*
|
||||
* @param args {filename}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runRun(String args[], Logging l) {
|
||||
if(args.length==1) {
|
||||
try {
|
||||
BufferedReader br =
|
||||
new BufferedReader(new FileReader(args[0]));
|
||||
String line;
|
||||
while((line = br.readLine()) != null) {
|
||||
runCommand(line,l);
|
||||
}
|
||||
br.close();
|
||||
notifyEvent("runResult", "ok");
|
||||
} catch (IOException ioe) {
|
||||
l.log("IO error running the file");
|
||||
_log.error("Error running the file", ioe);
|
||||
notifyEvent("runResult", "error");
|
||||
}
|
||||
} else {
|
||||
l.log("run <commandfile>");
|
||||
l.log(" loads commandfile and runs each line in it. \n"+
|
||||
" You can also give the filename on the commandline.");
|
||||
notifyEvent("runResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a lookup of the name specified
|
||||
*
|
||||
* Sets the event "lookupResult" = base64 of the destination, or an error message
|
||||
*
|
||||
* @param args {name}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runLookup(String args[], Logging l) {
|
||||
if (args.length != 1) {
|
||||
l.log("lookup <name>");
|
||||
l.log(" try to resolve the name into a destination key");
|
||||
notifyEvent("lookupResult", "invalidUsage");
|
||||
} else {
|
||||
String target = args[0];
|
||||
try {
|
||||
Destination dest = destFromName(args[0]);
|
||||
if (dest == null) {
|
||||
l.log("Unknown host");
|
||||
notifyEvent("lookupResult", "unkown host");
|
||||
} else {
|
||||
l.log(dest.toBase64());
|
||||
notifyEvent("lookupResult", dest.toBase64());
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
l.log("Unknown or invalid host");
|
||||
notifyEvent("lookupResult", "invalid host");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up a ping task with the specified args (currently supporting -ns, -h, -l)
|
||||
*
|
||||
* Sets the event "pingTaskId" = Integer of the taskId, or -1
|
||||
*
|
||||
* @param allargs arguments to pass to the I2Ping task
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runPing(String allargs, Logging l) {
|
||||
if(allargs.length() != 0) {
|
||||
I2PTunnelTask task;
|
||||
// pings always use the main destination
|
||||
task = new I2Ping(allargs, l, false, (EventDispatcher)this);
|
||||
addtask(task);
|
||||
notifyEvent("pingTaskId", new Integer(task.getId()));
|
||||
} else {
|
||||
l.log("ping <opts> <dest>");
|
||||
l.log("ping <opts> -h");
|
||||
l.log("ping <opts> -l <destlistfile>");
|
||||
l.log(" Tests communication with peers.\n"+
|
||||
" opts can be -ns (nosync) or not.");
|
||||
notifyEvent("pingTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to actually close the given task number (optionally forcing
|
||||
* closure)
|
||||
*
|
||||
*/
|
||||
private boolean closetask(int num, boolean forced, Logging l) {
|
||||
boolean closed = false;
|
||||
|
||||
_log.debug("closetask(): looking for task " + num);
|
||||
synchronized (tasks) {
|
||||
for (Iterator it=tasks.iterator(); it.hasNext();) {
|
||||
I2PTunnelTask t = (I2PTunnelTask) it.next();
|
||||
int id = t.getId();
|
||||
_log.debug("closetask(): parsing task " + id + " (" +
|
||||
t.toString() + ")");
|
||||
if (id == num) {
|
||||
closed = closetask(t, forced, l);
|
||||
break;
|
||||
} else if (id > num) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to actually close the given task number
|
||||
* (optionally forcing closure)
|
||||
*
|
||||
*/
|
||||
private boolean closetask(I2PTunnelTask t, boolean forced, Logging l) {
|
||||
l.log("Closing task " + t.getId() + (forced ? " forced..." : "..."));
|
||||
if (t.close(forced)) {
|
||||
l.log("Task " + t.getId() + " closed.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper task to remove closed / completed tasks.
|
||||
*
|
||||
*/
|
||||
private void purgetasks(Logging l) {
|
||||
synchronized (tasks) {
|
||||
for (Iterator it=tasks.iterator(); it.hasNext();) {
|
||||
I2PTunnelTask t = (I2PTunnelTask) it.next();
|
||||
if (!t.isOpen()) {
|
||||
_log.debug("Purging inactive tunnel: ["
|
||||
+ t.getId() + "] "
|
||||
+ t.toString());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given message (using both the logging subsystem and standard output...)
|
||||
*
|
||||
*/
|
||||
public void log(String s) {
|
||||
System.out.println(s);
|
||||
_log.info("Display: " + s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new destination, storing the destination and its private keys where
|
||||
* instructed
|
||||
*
|
||||
* @param writeTo location to store the private keys
|
||||
* @param pubDest location to store the destination
|
||||
* @param l logger to send messages to
|
||||
*/
|
||||
public static void makeKey(OutputStream writeTo, OutputStream pubDest,
|
||||
Logging l) {
|
||||
try {
|
||||
l.log("Generating new keys...");
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream(),
|
||||
pub = new ByteArrayOutputStream();
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination d = client.createDestination(writeTo);
|
||||
l.log("Secret key saved.");
|
||||
l.log("Public key: "+d.toBase64());
|
||||
writeTo.flush();
|
||||
writeTo.close();
|
||||
writePubKey(d, pubDest, l);
|
||||
} catch (I2PException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in the given destination, display it, and write it to the given location
|
||||
*
|
||||
* @param readFrom stream to read the destination from
|
||||
* @param pubDest stream to write the destination to
|
||||
* @param l logger to send messages to
|
||||
*/
|
||||
public static void showKey(InputStream readFrom, OutputStream pubDest,
|
||||
Logging l) {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination d = new Destination();
|
||||
d.readBytes(readFrom);
|
||||
l.log("Public key: "+d.toBase64());
|
||||
readFrom.close();
|
||||
writePubKey(d, pubDest, l);
|
||||
} catch (I2PException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the destination to the stream
|
||||
*
|
||||
* @param d Destination to write
|
||||
* @param o stream to write the destination to
|
||||
* @param l logger to send messages to
|
||||
*/
|
||||
private static void writePubKey(Destination d, OutputStream o, Logging l)
|
||||
throws I2PException, IOException {
|
||||
if (o==null) return;
|
||||
d.writeBytes(o);
|
||||
l.log("Public key saved.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Destination from a name. Now only supports base64
|
||||
* names - may support naming servers later. "file:<filename>" is
|
||||
* also supported, where filename is a file that either contains a
|
||||
* binary Destination structure or the Base64 encoding of that
|
||||
* structure.
|
||||
*/
|
||||
public static Destination destFromName(String name)
|
||||
throws DataFormatException {
|
||||
|
||||
if ( (name == null) || (name.trim().length() <= 0) )
|
||||
throw new DataFormatException("Empty destination provided");
|
||||
|
||||
if (name.startsWith("file:")) {
|
||||
Destination result=new Destination();
|
||||
byte content[] = null;
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(name.substring("file:".length()));
|
||||
byte buf[] = new byte[1024];
|
||||
int read = DataHelper.read(in, buf);
|
||||
content = new byte[read];
|
||||
System.arraycopy(buf, 0, content, 0, read);
|
||||
} catch (IOException ioe) {
|
||||
System.out.println(ioe.getMessage());
|
||||
return null;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException io) {}
|
||||
}
|
||||
try {
|
||||
result.fromByteArray(content);
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("File is not a binary destination - trying base64");
|
||||
try {
|
||||
byte decoded[] = Base64.decode(new String(content));
|
||||
result.fromByteArray(decoded);
|
||||
return result;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("File is not a base64 destination either - failing!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ask naming service
|
||||
NamingService inst = NamingService.getInstance();
|
||||
return inst.lookup(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void addConnectionEventListener(ConnectionEventListener lsnr) {
|
||||
if (lsnr == null) return;
|
||||
synchronized (listeners) {
|
||||
listeners.add(lsnr);
|
||||
}
|
||||
}
|
||||
public void removeConnectionEventListener(ConnectionEventListener lsnr) {
|
||||
if (lsnr == null) return;
|
||||
synchronized (listeners) {
|
||||
listeners.remove(lsnr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this whenever we lose touch with the router involuntarily (aka the router
|
||||
* is off / crashed / etc)
|
||||
*
|
||||
*/
|
||||
void routerDisconnected() {
|
||||
_log.error("Router disconnected - firing notification events");
|
||||
synchronized (listeners) {
|
||||
for (Iterator iter = listeners.iterator(); iter.hasNext();) {
|
||||
ConnectionEventListener lsnr = (ConnectionEventListener)iter.next();
|
||||
if (lsnr != null)
|
||||
lsnr.routerDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback routine to find out
|
||||
*/
|
||||
public interface ConnectionEventListener {
|
||||
public void routerDisconnected();
|
||||
}
|
||||
|
||||
/* Required by the EventDispatcher interface */
|
||||
public EventDispatcher getEventDispatcher() { return _event; }
|
||||
public void attachEventDispatcher(EventDispatcher e) { _event.attachEventDispatcher(e.getEventDispatcher()); }
|
||||
public void detachEventDispatcher(EventDispatcher e) { _event.detachEventDispatcher(e.getEventDispatcher()); }
|
||||
public void notifyEvent(String e, Object a) { _event.notifyEvent(e,a); }
|
||||
public Object getEventValue(String n) { return _event.getEventValue(n); }
|
||||
public Set getEvents() { return _event.getEvents(); }
|
||||
public void ignoreEvents() { _event.ignoreEvents(); }
|
||||
public void unIgnoreEvents() { _event.unIgnoreEvents(); }
|
||||
public Object waitEventValue(String n) { return _event.waitEventValue(n); }
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelClient.class);
|
||||
|
||||
protected Destination dest;
|
||||
|
||||
public I2PTunnelClient(int localPort, String destination,
|
||||
Logging l, boolean ownDest,
|
||||
EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "SynSender");
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
dest=I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
return;
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
l.log("Bad format in destination \"" + destination + "\".");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> " + destination);
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openClientResult", "ok");
|
||||
}
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null);
|
||||
} catch (I2PException ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
l.log("Unable to reach peer");
|
||||
// s has been initialized before the try block...
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,292 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public abstract class I2PTunnelClientBase extends I2PTunnelTask
|
||||
implements Runnable {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelClientBase.class);
|
||||
protected Logging l;
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 60*1000;
|
||||
|
||||
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
|
||||
private I2PSocketManager sockMgr;
|
||||
private List mySockets = new ArrayList();
|
||||
|
||||
protected Destination dest = null;
|
||||
private int localPort;
|
||||
|
||||
private boolean listenerReady = false;
|
||||
|
||||
private ServerSocket ss;
|
||||
|
||||
private Object startLock = new Object();
|
||||
private boolean startRunning = false;
|
||||
|
||||
private Object closeLock = new Object();
|
||||
|
||||
private byte[] pubkey;
|
||||
|
||||
private String handlerName;
|
||||
|
||||
//public I2PTunnelClientBase(int localPort, boolean ownDest,
|
||||
// Logging l) {
|
||||
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
public I2PTunnelClientBase(int localPort, boolean ownDest,
|
||||
Logging l, EventDispatcher notifyThis,
|
||||
String handlerName) {
|
||||
super(localPort+" (uninitialized)", notifyThis);
|
||||
this.localPort=localPort;
|
||||
this.l = l;
|
||||
this.handlerName=handlerName;
|
||||
|
||||
synchronized(sockLock) {
|
||||
if (ownDest) {
|
||||
sockMgr=buildSocketManager();
|
||||
} else {
|
||||
sockMgr=getSocketManager();
|
||||
}
|
||||
}
|
||||
if (sockMgr == null) throw new NullPointerException();
|
||||
l.log("I2P session created");
|
||||
|
||||
Thread t = new Thread(this);
|
||||
t.setName("Client");
|
||||
listenerReady=false;
|
||||
t.start();
|
||||
open=true;
|
||||
synchronized (this) {
|
||||
while (!listenerReady) {
|
||||
try {
|
||||
wait();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (open && listenerReady) {
|
||||
l.log("Ready! Port " + getLocalPort());
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Error!");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
protected static synchronized I2PSocketManager getSocketManager() {
|
||||
if (socketManager == null) {
|
||||
socketManager = buildSocketManager();
|
||||
}
|
||||
return socketManager;
|
||||
}
|
||||
|
||||
protected static I2PSocketManager buildSocketManager() {
|
||||
Properties props = new Properties();
|
||||
props.putAll(System.getProperties());
|
||||
return I2PSocketManagerFactory.createManager
|
||||
(I2PTunnel.host, Integer.parseInt(I2PTunnel.port), props);
|
||||
}
|
||||
|
||||
public final int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
|
||||
protected final InetAddress getListenHost(Logging l) {
|
||||
try {
|
||||
return InetAddress.getByName(I2PTunnel.listenHost);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("Could not find listen host to bind to [" +
|
||||
I2PTunnel.host + "]");
|
||||
_log.error("Error finding host to bind", uhe);
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually start working on incoming connections. *Must* be
|
||||
* called by derived classes after initialization.
|
||||
*
|
||||
*/
|
||||
public final void startRunning() {
|
||||
synchronized (startLock) {
|
||||
startRunning = true;
|
||||
startLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
private I2PSocketOptions getDefaultOptions() {
|
||||
I2PSocketOptions opts = new I2PSocketOptions();
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new I2PSocket towards to the specified destination,
|
||||
* adding it to the list of connections actually managed by this
|
||||
* tunnel.
|
||||
*
|
||||
* @param dest The destination to connect to
|
||||
* @return a new I2PSocket
|
||||
*/
|
||||
public I2PSocket createI2PSocket(Destination dest) throws I2PException {
|
||||
return createI2PSocket(dest, getDefaultOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new I2PSocket towards to the specified destination,
|
||||
* adding it to the list of connections actually managed by this
|
||||
* tunnel.
|
||||
*
|
||||
* @param dest The destination to connect to
|
||||
* @param opt Option to be used to open when opening the socket
|
||||
* @return a new I2PSocket
|
||||
*/
|
||||
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException {
|
||||
I2PSocket i2ps;
|
||||
|
||||
synchronized (sockLock) {
|
||||
i2ps = sockMgr.connect(dest, opt);
|
||||
mySockets.add(i2ps);
|
||||
}
|
||||
|
||||
return i2ps;
|
||||
}
|
||||
|
||||
public final void run() {
|
||||
try {
|
||||
InetAddress addr = getListenHost(l);
|
||||
if (addr == null) return;
|
||||
ss = new ServerSocket(localPort, 0, addr);
|
||||
|
||||
// If a free port was requested, find out what we got
|
||||
if (localPort == 0) {
|
||||
localPort = ss.getLocalPort();
|
||||
}
|
||||
notifyEvent("clientLocalPort", new Integer(ss.getLocalPort()));
|
||||
l.log("Listening for clients on port " + localPort +
|
||||
" of " + I2PTunnel.listenHost);
|
||||
|
||||
// Notify constructor that port is ready
|
||||
synchronized(this) {
|
||||
listenerReady = true;
|
||||
notify();
|
||||
}
|
||||
|
||||
// Wait until we are authorized to process data
|
||||
synchronized (startLock) {
|
||||
while (!startRunning) {
|
||||
try {
|
||||
startLock.wait();
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
manageConnection(s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error listening for connections", ex);
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the connection just opened on the specified socket
|
||||
*
|
||||
* @param s Socket to take care of
|
||||
*/
|
||||
protected void manageConnection(Socket s) {
|
||||
new ClientConnectionRunner(s, handlerName);
|
||||
}
|
||||
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
if (!open) return true;
|
||||
// FIXME: here we might have to wait quite a long time if
|
||||
// there is a connection attempt atm. But without waiting we
|
||||
// might risk to create an orphan socket. Would be better
|
||||
// to return with an error in that situation quickly.
|
||||
synchronized(sockLock) {
|
||||
mySockets.retainAll(sockMgr.listSockets());
|
||||
if (!forced && mySockets.size() != 0) {
|
||||
l.log("There are still active connections!");
|
||||
_log.debug("can't close: there are still active connections!");
|
||||
for (Iterator it = mySockets.iterator(); it.hasNext();) {
|
||||
l.log("->"+it.next());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
l.log("Closing client "+toString());
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
l.log("Client closed.");
|
||||
open=false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeSocket(Socket s) {
|
||||
try {
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Could not close socket", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public class ClientConnectionRunner extends Thread {
|
||||
private Socket s;
|
||||
|
||||
public ClientConnectionRunner(Socket s, String name) {
|
||||
this.s=s;
|
||||
setName(name);
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
clientConnectionRun(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage a connection in a separate thread. This only works if
|
||||
* you do not override manageConnection()
|
||||
*/
|
||||
protected abstract void clientConnectionRun(Socket s);
|
||||
}
|
48
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelGUI.java
Normal file
48
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelGUI.java
Normal file
@ -0,0 +1,48 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Font;
|
||||
import java.awt.Frame;
|
||||
import java.awt.TextArea;
|
||||
import java.awt.TextField;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* AWT gui since kaffe doesn't support swing yet
|
||||
*/
|
||||
public class I2PTunnelGUI extends Frame implements ActionListener, Logging {
|
||||
|
||||
TextField input;
|
||||
TextArea log;
|
||||
I2PTunnel t;
|
||||
|
||||
public I2PTunnelGUI(I2PTunnel t) {
|
||||
super("I2PTunnel control panel");
|
||||
this.t=t;
|
||||
setLayout(new BorderLayout());
|
||||
add("South", input=new TextField());
|
||||
input.addActionListener(this);
|
||||
Font font = new Font("Monospaced",Font.PLAIN,12);
|
||||
add("Center",log=new TextArea("",20,80,TextArea.SCROLLBARS_VERTICAL_ONLY));
|
||||
log.setFont(font);
|
||||
log.setEditable(false);
|
||||
log("enter 'help' for help.");
|
||||
pack();
|
||||
show();
|
||||
}
|
||||
|
||||
public void log(String s) {
|
||||
log.append(s+"\n");
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
log("I2PTunnel>"+input.getText());
|
||||
t.runCommand(input.getText(), this);
|
||||
log("---");
|
||||
input.setText("");
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelClientBase
|
||||
implements Runnable {
|
||||
private static final Log _log =
|
||||
new Log(I2PTunnelHTTPClient.class);
|
||||
|
||||
private String wwwProxy;
|
||||
|
||||
private final static byte[] ERR_REQUEST_DENIED = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=iso-8859-1\r\nCache-control: no-cache\r\n\r\n<html><body><H1>I2P ERROR: REQUEST DENIED</H1>You attempted to connect to a non-I2P website or location.<BR>".getBytes();
|
||||
private final static byte[] ERR_DESTINATION_UNKNOWN = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=iso-8859-1\r\nCache-control: no-cache\r\n\r\n<html><body><H1>I2P ERROR: NOT FOUND</H1>That Desitination was not found. Perhaps you pasted in the wrong BASE64 I2P Destination or the link you are following is bad. The host (or the WWW proxy, if you're using one) could also be temporarily offline. Could not find the following Destination:<BR><BR>".getBytes();
|
||||
private final static byte[] ERR_TIMEOUT = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=iso-8859-1\r\nCache-control: no-cache\r\n\r\n<html><body><H1>I2P ERROR: TIMEOUT</H1>That Desitination was reachable, but timed out getting a response. This may be a temporary error, so you should simply try to refresh, though if the problem persists, the remote destination may have issues. Could not get a response from the following Destination:<BR><BR>".getBytes();
|
||||
|
||||
//public I2PTunnelHTTPClient(int localPort, Logging l,
|
||||
// boolean ownDest,
|
||||
// String wwwProxy) {
|
||||
// I2PTunnelHTTPClient(localPort, l, ownDest, wwwProxy,
|
||||
// (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l,
|
||||
boolean ownDest,
|
||||
String wwwProxy, EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler");
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openHTTPClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
this.wwwProxy = wwwProxy;
|
||||
|
||||
setName(getLocalPort()
|
||||
+ " -> HTTPClient [WWW outproxy: " + this.wwwProxy + "]");
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
OutputStream out = null;
|
||||
String targetRequest = null;
|
||||
boolean usingWWWProxy = false;
|
||||
InactivityTimeoutThread timeoutThread = null;
|
||||
try {
|
||||
out = s.getOutputStream();
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader(s.getInputStream(),
|
||||
"ISO-8859-1"));
|
||||
String line, method=null, protocol=null, host=null, destination=null;
|
||||
StringBuffer newRequest=new StringBuffer();
|
||||
while ((line=br.readLine()) != null) {
|
||||
if (method==null) { // first line (GET /base64/realaddr)
|
||||
int pos=line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method=line.substring(0, pos);
|
||||
String request = line.substring(pos+1);
|
||||
if (request.startsWith("/") &&
|
||||
System.getProperty("i2ptunnel.noproxy") != null) {
|
||||
request="http://i2p"+request;
|
||||
}
|
||||
pos = request.indexOf("//");
|
||||
if (pos == -1) {
|
||||
method=null;
|
||||
break;
|
||||
}
|
||||
protocol=request.substring(0,pos+2);
|
||||
request=request.substring(pos+2);
|
||||
|
||||
targetRequest = request;
|
||||
|
||||
pos = request.indexOf("/");
|
||||
if (pos == -1) {
|
||||
method=null;
|
||||
break;
|
||||
}
|
||||
host=request.substring(0,pos);
|
||||
|
||||
// Quick hack for foo.bar.i2p
|
||||
if (host.toLowerCase().endsWith( ".i2p")) {
|
||||
destination=host;
|
||||
host=getHostName(destination);
|
||||
line=method+" "+request.substring(pos);
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// The request must be forwarded to a WWW proxy
|
||||
destination = wwwProxy;
|
||||
usingWWWProxy = true;
|
||||
} else {
|
||||
request=request.substring(pos+1);
|
||||
pos = request.indexOf("/");
|
||||
destination=request.substring(0,pos);
|
||||
line=method+" "+request.substring(pos);
|
||||
}
|
||||
|
||||
boolean isValid = usingWWWProxy ||
|
||||
isSupportedAddress(host, protocol);
|
||||
if (!isValid) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("notValid(" + host + ")");
|
||||
method=null;
|
||||
destination=null;
|
||||
break;
|
||||
} else if (!usingWWWProxy) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("host=getHostName(" + destination + ")");
|
||||
host=getHostName(destination); // hide original host
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("METHOD:"+method+":");
|
||||
_log.debug("PROTOC:"+protocol+":");
|
||||
_log.debug("HOST :"+host+":");
|
||||
_log.debug("DEST :"+destination+":");
|
||||
}
|
||||
|
||||
} else if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
line="Host: "+host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting host = " + host);
|
||||
}
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
if (line.length()==0) break;
|
||||
}
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i=br.read();
|
||||
if (i != -1) {
|
||||
newRequest.append((char)i);
|
||||
}
|
||||
}
|
||||
if (method==null || destination==null) {
|
||||
l.log("No HTTP method found in the 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 dest=I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve "+destination+".");
|
||||
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest,
|
||||
usingWWWProxy, destination);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
String remoteID;
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
byte[] data=newRequest.toString().getBytes("ISO-8859-1");
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data);
|
||||
timeoutThread = new InactivityTimeoutThread(runner, out, targetRequest, usingWWWProxy, s);
|
||||
timeoutThread.start();
|
||||
} catch (IOException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.error("Error sending syn", ex);
|
||||
handleHTTPClientException(ex, out, targetRequest,
|
||||
usingWWWProxy, wwwProxy);
|
||||
closeSocket(s);
|
||||
} catch (I2PException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info("Error sending syn", ex);
|
||||
l.log("Unable to reach peer");
|
||||
handleHTTPClientException(ex, out, targetRequest,
|
||||
usingWWWProxy, wwwProxy);
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
private static final long INACTIVITY_TIMEOUT = 120*1000;
|
||||
|
||||
private class InactivityTimeoutThread extends I2PThread {
|
||||
|
||||
private Socket s;
|
||||
private I2PTunnelRunner _runner;
|
||||
private OutputStream _out;
|
||||
private String _targetRequest;
|
||||
private boolean _useWWWProxy;
|
||||
private boolean _disabled;
|
||||
private Object _disableLock = new Object();
|
||||
|
||||
public InactivityTimeoutThread(I2PTunnelRunner runner, OutputStream out, String targetRequest, boolean useWWWProxy, Socket s) {
|
||||
this.s=s;
|
||||
_runner = runner;
|
||||
_out = out;
|
||||
_targetRequest = targetRequest;
|
||||
_useWWWProxy = useWWWProxy;
|
||||
_disabled = false;
|
||||
}
|
||||
public void disable() {
|
||||
_disabled = true;
|
||||
synchronized (_disableLock) { _disableLock.notifyAll(); }
|
||||
}
|
||||
public void run() {
|
||||
while (!_disabled) {
|
||||
if (_runner.isFinished()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HTTP client request completed prior to timeout");
|
||||
return;
|
||||
}
|
||||
if (_runner.getLastActivityOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
|
||||
if (_runner.getStartedOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("HTTP client request timed out (lastActivity: " + new Date(_runner.getLastActivityOn()) + ", startedOn: " + new Date(_runner.getLastActivityOn()) + ")");
|
||||
timeout();
|
||||
return;
|
||||
} else {
|
||||
// runner hasn't been going to long enough
|
||||
}
|
||||
} else {
|
||||
// there has been activity in the period
|
||||
}
|
||||
synchronized (_disableLock) {
|
||||
try {
|
||||
_disableLock.wait(INACTIVITY_TIMEOUT);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void timeout() {
|
||||
_log.info("Inactivity timeout reached");
|
||||
l.log("Inactivity timeout reached");
|
||||
if (_out != null) {
|
||||
try {
|
||||
if (_runner.getLastActivityOn() > 0) {
|
||||
// some data has been sent, so don't 404 it
|
||||
} else {
|
||||
writeErrorMessage(ERR_TIMEOUT, _out, _targetRequest,
|
||||
_useWWWProxy, wwwProxy);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error writing out the 'timeout' message", ioe);
|
||||
}
|
||||
} else {
|
||||
_log.warn("Client disconnected before we could say we timed out");
|
||||
}
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
private final static String getHostName(String host) {
|
||||
try {
|
||||
Destination dest=I2PTunnel.destFromName(host);
|
||||
if (dest == null) return "i2p";
|
||||
return dest.toBase64();
|
||||
} catch (DataFormatException dfe) {
|
||||
return "i2p";
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out,
|
||||
String targetRequest,
|
||||
boolean usingWWWProxy,
|
||||
String wwwProxy)
|
||||
throws IOException {
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
out.write(targetRequest.getBytes());
|
||||
if (usingWWWProxy)
|
||||
out.write(("<br>WWW proxy: " +
|
||||
wwwProxy).getBytes());
|
||||
}
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleHTTPClientException (Exception ex, OutputStream out,
|
||||
String targetRequest,
|
||||
boolean usingWWWProxy,
|
||||
String wwwProxy) {
|
||||
if (out != null) {
|
||||
try {
|
||||
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest,
|
||||
usingWWWProxy, wwwProxy);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error writing out the 'destination was unknown' "+
|
||||
"message", ioe);
|
||||
}
|
||||
} else {
|
||||
_log.warn("Client disconnected before we could say that destination "+
|
||||
"was unknown", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final static String SUPPORTED_HOSTS[] = { "i2p", "www.i2p.com",
|
||||
"i2p." };
|
||||
|
||||
private boolean isSupportedAddress(String host, String protocol) {
|
||||
if ( (host == null) || (protocol == null) ) return false;
|
||||
boolean found = false;
|
||||
String lcHost = host.toLowerCase();
|
||||
for (int i = 0; i < SUPPORTED_HOSTS.length; i++) {
|
||||
if (SUPPORTED_HOSTS[i].equals(lcHost)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
try {
|
||||
Destination d = I2PTunnel.destFromName(host);
|
||||
if (d == null) return false;
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
|
||||
return protocol.equalsIgnoreCase("http://");
|
||||
}
|
||||
}
|
184
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
Normal file
184
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
Normal file
@ -0,0 +1,184 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
public class I2PTunnelRunner extends Thread {
|
||||
private final static Log _log = new Log(I2PTunnelRunner.class);
|
||||
|
||||
/**
|
||||
* max bytes streamed in a packet - smaller ones might be filled
|
||||
* up to this size. Larger ones are not split (at least not on
|
||||
* Sun's impl of BufferedOutputStream), but that is the streaming
|
||||
* api's job...
|
||||
*/
|
||||
static int MAX_PACKET_SIZE = 1024*32;
|
||||
|
||||
static final int NETWORK_BUFFER_SIZE = MAX_PACKET_SIZE;
|
||||
|
||||
private Socket s;
|
||||
private I2PSocket i2ps;
|
||||
Object slock, finishLock = new Object();
|
||||
boolean finished=false;
|
||||
HashMap ostreams, sockets;
|
||||
I2PSession session;
|
||||
byte[] initialData;
|
||||
/** when the last data was sent/received (or -1 if never) */
|
||||
private long lastActivityOn;
|
||||
/** when the runner started up */
|
||||
private long startedOn;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock,
|
||||
byte[] initialData) {
|
||||
this.s=s;
|
||||
this.i2ps=i2ps;
|
||||
this.slock=slock;
|
||||
this.initialData = initialData;
|
||||
lastActivityOn = -1;
|
||||
startedOn = -1;
|
||||
_log.info("I2PTunnelRunner started");
|
||||
setName("I2PTunnelRunner");
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* have we closed at least one (if not both) of the streams
|
||||
* [aka we're done running the streams]?
|
||||
*
|
||||
*/
|
||||
public boolean isFinished() { return finished; }
|
||||
|
||||
/**
|
||||
* When was the last data for this runner sent or received? (-1 if no data
|
||||
* has been transferred yet)
|
||||
*
|
||||
*/
|
||||
public long getLastActivityOn() { return lastActivityOn; }
|
||||
private void updateActivity() { lastActivityOn = Clock.getInstance().now(); }
|
||||
|
||||
/**
|
||||
* When this runner started up transferring data
|
||||
*
|
||||
*/
|
||||
public long getStartedOn() { return startedOn; }
|
||||
|
||||
public void run() {
|
||||
startedOn = Clock.getInstance().now();
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = new BufferedOutputStream(s.getOutputStream(),
|
||||
NETWORK_BUFFER_SIZE);
|
||||
InputStream i2pin = i2ps.getInputStream();
|
||||
OutputStream i2pout = new BufferedOutputStream
|
||||
(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialData != null) {
|
||||
synchronized(slock) {
|
||||
i2pout.write(initialData);
|
||||
i2pout.flush();
|
||||
}
|
||||
}
|
||||
Thread t1 = new StreamForwarder(in, i2pout);
|
||||
Thread t2 = new StreamForwarder(i2pin, out);
|
||||
synchronized(finishLock) {
|
||||
while (!finished) {
|
||||
finishLock.wait();
|
||||
}
|
||||
}
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
s = null;
|
||||
i2ps.close();
|
||||
i2ps = null;
|
||||
t1.join();
|
||||
t2.join();
|
||||
} catch (InterruptedException ex) {
|
||||
_log.error("Interrupted", ex);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.error("Error forwarding", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
if (i2ps != null) i2ps.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.error("Could not close socket", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StreamForwarder extends Thread {
|
||||
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
|
||||
private StreamForwarder(InputStream in, OutputStream out) {
|
||||
this.in=in;
|
||||
this.out=out;
|
||||
setName("StreamForwarder");
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buffer = new byte[NETWORK_BUFFER_SIZE];
|
||||
try {
|
||||
int len;
|
||||
while ((len=in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
|
||||
if (len > 0)
|
||||
updateActivity();
|
||||
|
||||
if (in.available()==0) {
|
||||
try {
|
||||
Thread.sleep(I2PTunnel.PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (in.available()==0) {
|
||||
out.flush(); // make sure the data get though
|
||||
}
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
// this *will* occur when the other threads closes the socket
|
||||
synchronized(finishLock) {
|
||||
if (!finished)
|
||||
_log.error("Error reading and writing", ex);
|
||||
else
|
||||
_log.warn("You may ignore this", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (!finished)
|
||||
_log.error("Error forwarding", ex);
|
||||
else
|
||||
_log.warn("You may ignore this", ex);
|
||||
} finally {
|
||||
try {
|
||||
out.close();
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error closing streams", ex);
|
||||
}
|
||||
synchronized(finishLock) {
|
||||
finished=true;
|
||||
finishLock.notifyAll();
|
||||
// the main thread will close sockets etc. now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
138
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
Normal file
138
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
Normal file
@ -0,0 +1,138 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelServer extends I2PTunnelTask
|
||||
implements Runnable {
|
||||
|
||||
private final static Log _log = new Log(I2PTunnelServer.class);
|
||||
|
||||
private I2PSocketManager sockMgr;
|
||||
private I2PServerSocket i2pss;
|
||||
|
||||
private Object lock = new Object(), slock = new Object();
|
||||
|
||||
private InetAddress remoteHost;
|
||||
private int remotePort;
|
||||
|
||||
private Logging l;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port,
|
||||
String privData, Logging l,
|
||||
EventDispatcher notifyThis) {
|
||||
super(host+":"+port+" <- "+privData, notifyThis);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
|
||||
init(host, port, bais, privData, l);
|
||||
}
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port,
|
||||
File privkey, String privkeyname,
|
||||
Logging l, EventDispatcher notifyThis) {
|
||||
super(host+":"+port+" <- "+privkeyname, notifyThis);
|
||||
try {
|
||||
init(host, port, new FileInputStream(privkey), privkeyname, l);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error starting server", ioe);
|
||||
notifyEvent("openServerResult", "error");
|
||||
}
|
||||
}
|
||||
public I2PTunnelServer(InetAddress host, int port,
|
||||
InputStream privData, String privkeyname,
|
||||
Logging l, EventDispatcher notifyThis) {
|
||||
super(host+":"+port+" <- "+privkeyname, notifyThis);
|
||||
init(host, port, privData, privkeyname, l);
|
||||
}
|
||||
|
||||
private void init(InetAddress host, int port, InputStream privData,
|
||||
String privkeyname, Logging l) {
|
||||
this.l=l;
|
||||
this.remoteHost=host;
|
||||
this.remotePort=port;
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Properties props = new Properties();
|
||||
props.putAll(System.getProperties());
|
||||
synchronized(slock) {
|
||||
sockMgr = I2PSocketManagerFactory.createManager
|
||||
(privData, I2PTunnel.host,
|
||||
Integer.parseInt(I2PTunnel.port), props);
|
||||
|
||||
}
|
||||
l.log("Ready!");
|
||||
notifyEvent("openServerResult", "ok");
|
||||
open=true;
|
||||
Thread t = new Thread(this);
|
||||
t.setName("Server");
|
||||
t.start();
|
||||
}
|
||||
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
if (!open) return true;
|
||||
synchronized(lock) {
|
||||
if (!forced && sockMgr.listSockets().size() != 0) {
|
||||
l.log("There are still active connections!");
|
||||
for (Iterator it = sockMgr.listSockets().iterator();
|
||||
it.hasNext();) {
|
||||
l.log("->"+it.next());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
l.log("Shutting down server "+toString());
|
||||
try {
|
||||
if (i2pss != null) i2pss.close();
|
||||
sockMgr.getSession().destroySession();
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error destroying the session", ex);
|
||||
System.exit(1);
|
||||
}
|
||||
l.log("Server shut down.");
|
||||
open=false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
while (true) {
|
||||
I2PSocket i2ps = i2pss.accept();
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
new I2PTunnelRunner(s, i2ps, slock, null);
|
||||
} catch (SocketException ex) {
|
||||
i2ps.close();
|
||||
}
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
75
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java
Normal file
75
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java
Normal file
@ -0,0 +1,75 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.EventDispatcherImpl;
|
||||
|
||||
/**
|
||||
* Either a Server or a Client.
|
||||
*/
|
||||
|
||||
public abstract class I2PTunnelTask implements EventDispatcher {
|
||||
|
||||
private final EventDispatcherImpl _event = new EventDispatcherImpl();
|
||||
|
||||
private int id;
|
||||
private String name;
|
||||
protected boolean open;
|
||||
private I2PTunnel tunnel;
|
||||
|
||||
//protected I2PTunnelTask(String name) {
|
||||
// I2PTunnelTask(name, (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
protected I2PTunnelTask(String name, EventDispatcher notifyThis) {
|
||||
attachEventDispatcher(notifyThis);
|
||||
this.name=name;
|
||||
this.id = -1;
|
||||
}
|
||||
|
||||
/** for apps that use multiple I2PTunnel instances */
|
||||
public void setTunnel(I2PTunnel pTunnel) { tunnel = pTunnel; }
|
||||
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isOpen() {return open;}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
protected void setName(String name) {
|
||||
this.name=name;
|
||||
}
|
||||
|
||||
protected void routerDisconnected() { tunnel.routerDisconnected(); }
|
||||
|
||||
public abstract boolean close(boolean forced);
|
||||
|
||||
public void disconnected(I2PSession session) { routerDisconnected(); }
|
||||
public void errorOccurred(I2PSession session, String message,
|
||||
Throwable error) {}
|
||||
public void reportAbuse(I2PSession session, int severity) {}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/* Required by the EventDispatcher interface */
|
||||
public EventDispatcher getEventDispatcher() { return _event; }
|
||||
public void attachEventDispatcher(EventDispatcher e) { _event.attachEventDispatcher(e.getEventDispatcher()); }
|
||||
public void detachEventDispatcher(EventDispatcher e) { _event.detachEventDispatcher(e.getEventDispatcher()); }
|
||||
public void notifyEvent(String e, Object a) { _event.notifyEvent(e,a); }
|
||||
public Object getEventValue(String n) { return _event.getEventValue(n); }
|
||||
public Set getEvents() { return _event.getEvents(); }
|
||||
public void ignoreEvents() { _event.ignoreEvents(); }
|
||||
public void unIgnoreEvents() { _event.unIgnoreEvents(); }
|
||||
public Object waitEventValue(String n) { return _event.waitEventValue(n); }
|
||||
}
|
228
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
Normal file
228
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
Normal file
@ -0,0 +1,228 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2Ping extends I2PTunnelTask implements Runnable {
|
||||
private final static Log _log = new Log(I2Ping.class);
|
||||
|
||||
private static final int PING_COUNT = 3;
|
||||
private static final int CPING_COUNT = 5;
|
||||
private static final int PING_TIMEOUT= 5000;
|
||||
|
||||
private static final long PING_DISTANCE=1000;
|
||||
|
||||
private int MAX_SIMUL_PINGS=10; // not really final...
|
||||
|
||||
private boolean countPing=false;
|
||||
|
||||
private I2PSocketManager sockMgr;
|
||||
private Logging l;
|
||||
private boolean finished=false;
|
||||
private String command;
|
||||
private long timeout = PING_TIMEOUT;
|
||||
|
||||
private Object simulLock = new Object();
|
||||
private int simulPings = 0;
|
||||
private long lastPingTime = 0;
|
||||
|
||||
private Object lock = new Object(), slock = new Object();
|
||||
|
||||
//public I2Ping(String cmd, Logging l,
|
||||
// boolean ownDest) {
|
||||
// I2Ping(cmd, l, (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
public I2Ping(String cmd, Logging l,
|
||||
boolean ownDest, EventDispatcher notifyThis) {
|
||||
super("I2Ping ["+cmd+"]", notifyThis);
|
||||
this.l=l;
|
||||
command=cmd;
|
||||
synchronized(slock) {
|
||||
if (ownDest) {
|
||||
sockMgr = I2PTunnelClient.buildSocketManager();
|
||||
} else {
|
||||
sockMgr = I2PTunnelClient.getSocketManager();
|
||||
}
|
||||
}
|
||||
Thread t = new Thread(this);
|
||||
t.setName("Client");
|
||||
t.start();
|
||||
open=true;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
l.log("*** I2Ping results:");
|
||||
try {
|
||||
runCommand(command);
|
||||
} catch (InterruptedException ex) {
|
||||
l.log("*** Interrupted");
|
||||
_log.error("Pinger interrupted",ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Pinger exception",ex);
|
||||
}
|
||||
l.log("*** Finished.");
|
||||
synchronized(lock) {
|
||||
finished=true;
|
||||
}
|
||||
close(false);
|
||||
}
|
||||
|
||||
public void runCommand(String cmd) throws InterruptedException,
|
||||
IOException {
|
||||
if (cmd.startsWith("-t ")) { // timeout
|
||||
cmd = cmd.substring(3);
|
||||
int pos = cmd.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
l.log("Syntax error");
|
||||
return;
|
||||
} else {
|
||||
timeout = Long.parseLong(cmd.substring(0, pos));
|
||||
cmd=cmd.substring(pos+1);
|
||||
}
|
||||
}
|
||||
if (cmd.startsWith("-m ")) { // max simultaneous pings
|
||||
cmd = cmd.substring(3);
|
||||
int pos = cmd.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
l.log("Syntax error");
|
||||
return;
|
||||
} else {
|
||||
MAX_SIMUL_PINGS = Integer.parseInt(cmd.substring(0, pos));
|
||||
cmd=cmd.substring(pos+1);
|
||||
}
|
||||
}
|
||||
if (cmd.startsWith("-c ")) { // "count" ping
|
||||
countPing=true;
|
||||
cmd=cmd.substring(3);
|
||||
}
|
||||
if (cmd.equals("-h")) { // ping all hosts
|
||||
cmd="-l hosts.txt";
|
||||
}
|
||||
if (cmd.startsWith("-l ")) { // ping a list of hosts
|
||||
BufferedReader br = new BufferedReader
|
||||
(new FileReader(cmd.substring(3)));
|
||||
String line;
|
||||
List pingHandlers = new ArrayList();
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith("#")) continue; // comments
|
||||
if (line.startsWith(";")) continue;
|
||||
if (line.startsWith("!")) continue;
|
||||
if (line.indexOf("=") != -1) { // maybe file is hosts.txt?
|
||||
line=line.substring(0,line.indexOf("="));
|
||||
}
|
||||
pingHandlers.add(new PingHandler(line));
|
||||
}
|
||||
br.close();
|
||||
for (Iterator it= pingHandlers.iterator(); it.hasNext(); ) {
|
||||
Thread t = (Thread) it.next();
|
||||
t.join();
|
||||
}
|
||||
|
||||
} else {
|
||||
Thread t = new PingHandler(cmd);
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
if (!open) return true;
|
||||
synchronized(lock) {
|
||||
if (!forced && !finished) {
|
||||
l.log("There are still pings running!");
|
||||
return false;
|
||||
}
|
||||
l.log("Closing pinger "+toString());
|
||||
l.log("Pinger closed.");
|
||||
open=false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean ping(Destination dest) throws I2PException {
|
||||
try {
|
||||
synchronized(simulLock) {
|
||||
while (simulPings >= MAX_SIMUL_PINGS) {
|
||||
simulLock.wait();
|
||||
}
|
||||
simulPings++;
|
||||
while (lastPingTime + PING_DISTANCE >
|
||||
System.currentTimeMillis()) {
|
||||
// no wait here, to delay all pingers
|
||||
Thread.sleep(PING_DISTANCE/2);
|
||||
}
|
||||
lastPingTime=System.currentTimeMillis();
|
||||
}
|
||||
boolean sent = sockMgr.ping(dest, PING_TIMEOUT);
|
||||
synchronized(simulLock) {
|
||||
simulPings--;
|
||||
simulLock.notifyAll();
|
||||
}
|
||||
return sent;
|
||||
} catch (InterruptedException ex) {
|
||||
_log.error("Interrupted", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class PingHandler extends Thread {
|
||||
private String destination;
|
||||
|
||||
public PingHandler(String dest) {
|
||||
this.destination=dest;
|
||||
setName("PingHandler for " + dest);
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Destination dest=I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
synchronized(lock) { // Logger is not thread safe
|
||||
l.log("Unresolvable: "+destination+"");
|
||||
}
|
||||
return;
|
||||
}
|
||||
int cnt = countPing ? CPING_COUNT : PING_COUNT;
|
||||
StringBuffer pingResults = new StringBuffer
|
||||
(2*cnt+ destination.length()+3);
|
||||
for (int i=0;i<cnt; i++) {
|
||||
boolean sent;
|
||||
sent = ping(dest);
|
||||
if (countPing) {
|
||||
if (!sent) {
|
||||
pingResults.append(i).append(" ");
|
||||
break;
|
||||
} else if (i == cnt - 1) {
|
||||
pingResults.append("+ ");
|
||||
}
|
||||
} else {
|
||||
pingResults.append(sent?"+ ":"- ");
|
||||
}
|
||||
// System.out.println(sent+" -> "+destination);
|
||||
}
|
||||
pingResults.append(" ").append(destination);
|
||||
synchronized(lock) { // Logger is not thread safe
|
||||
l.log(pingResults.toString());
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error pinging " + destination, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/Logging.java
Normal file
9
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/Logging.java
Normal file
@ -0,0 +1,9 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
|
||||
public interface Logging {
|
||||
public void log(String s);
|
||||
}
|
433
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelManager.java
Normal file
433
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelManager.java
Normal file
@ -0,0 +1,433 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Quick and dirty socket listener to control an I2PTunnel.
|
||||
* Basically run this class as TunnelManager [listenHost] [listenPort] and
|
||||
* then send it commands on that port. Commands are one shot deals -
|
||||
* Send a command + newline, get a response plus newline, then get disconnected.
|
||||
* <p />
|
||||
* <b>Implemented commands:</b>
|
||||
* <pre>
|
||||
* -------------------------------------------------
|
||||
* lookup <name>\n
|
||||
* --
|
||||
* <base64 of the destination>\n
|
||||
* or
|
||||
* <error message, usually 'Unknown host'>\n
|
||||
*
|
||||
* Lookup the public key of a named destination (i.e. listed in hosts.txt)
|
||||
* -------------------------------------------------
|
||||
* genkey\n
|
||||
* --
|
||||
* <base64 of the destination>\t<base64 of private data>\n
|
||||
*
|
||||
* Generates a new public and private key pair
|
||||
* -------------------------------------------------
|
||||
* convertprivate <base64 of privkey>
|
||||
* --
|
||||
* <base64 of destination>\n
|
||||
* or
|
||||
* <error message>\n
|
||||
*
|
||||
* Returns the destination (pubkey) of a given private key.
|
||||
* -------------------------------------------------
|
||||
* listen_on <ip>\n
|
||||
* --
|
||||
* ok\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Sets the ip address clients will listen on. By default this is the
|
||||
* localhost (127.0.0.1)
|
||||
* -------------------------------------------------
|
||||
* openclient <listenPort> <peer>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open a tunnel on the given <listenport> to the destination specified
|
||||
* by <peer>. If <listenPort> is 0 a free port is picked and returned in
|
||||
* the reply message. Otherwise the short reply message is used.
|
||||
* Peer can be the base64 of the destination, a file with the public key
|
||||
* specified as 'file:<filename>' or the name of a destination listed in
|
||||
* hosts.txt. The <jobId> returned together with "ok" and <listenport> can
|
||||
* later be used as argument for the "close" command.
|
||||
* -------------------------------------------------
|
||||
* openhttpclient <listenPort> [<proxy>]\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open an HTTP proxy through the I2P on the given
|
||||
* <listenport>. <proxy> (optional) specifies a
|
||||
* destination to be used as an outbound proxy, to access normal WWW
|
||||
* sites out of the .i2p domain. If <listenPort> is 0 a free
|
||||
* port is picked and returned in the reply message. Otherwise the
|
||||
* short reply message is used. <proxy> can be the base64 of the
|
||||
* destination, a file with the public key specified as
|
||||
* 'file:<filename>' or the name of a destination listed in
|
||||
* hosts.txt. The <jobId> returned together with "ok" and
|
||||
* <listenport> can later be used as argument for the "close"
|
||||
* command.
|
||||
* -------------------------------------------------
|
||||
* opensockstunnel <listenPort>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open an SOCKS tunnel through the I2P on the given
|
||||
* <listenport>. If <listenPort> is 0 a free port is
|
||||
* picked and returned in the reply message. Otherwise the short
|
||||
* reply message is used. The <jobId> returned together with
|
||||
* "ok" and <listenport> can later be used as argument for the
|
||||
* "close" command.
|
||||
* -------------------------------------------------
|
||||
* openserver <serverHost> <serverPort> <serverKeys>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Starts receiving traffic for the destination specified by <serverKeys>
|
||||
* and forwards it to the <serverPort> of <serverHost>.
|
||||
* <serverKeys> is the base 64 encoded private key set of the local
|
||||
* destination. The <joId> returned together with "ok" can later be used
|
||||
* as argument for the "close" command.
|
||||
* -------------------------------------------------
|
||||
* close [forced] <jobId>\n
|
||||
* or
|
||||
* close [forced] all\n
|
||||
* --
|
||||
* ok\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Closes the job specified by <jobId> or all jobs. Use the list command
|
||||
* for a list of running jobs.
|
||||
* Normally a connection job is not closed when it still has an active
|
||||
* connection. Use the optional 'forced' keyword to close connections
|
||||
* regardless of their use.
|
||||
* -------------------------------------------------
|
||||
* list\n
|
||||
* --
|
||||
* Example output:
|
||||
*
|
||||
* [0] i2p.dnsalias.net/69.55.226.145:5555 <- C:\i2pKeys\squidPriv
|
||||
* [1] 8767 -> HTTPClient
|
||||
* [2] 7575 -> file:C:\i2pKeys\squidPub
|
||||
* [3] 5252 -> sCcSANIO~f4AQtCNI1BvDp3ZBS~9Ag5O0k0Msm7XBWWz5eOnZWL3MQ-2rxlesucb9XnpASGhWzyYNBpWAfaIB3pux1J1xujQLOwscMIhm7T8BP76Ly5jx6BLZCYrrPj0BI0uV90XJyT~4UyQgUlC1jzFQdZ9HDgBPJDf1UI4-YjIwEHuJgdZynYlQ1oUFhgno~HhcDByXO~PDaO~1JDMDbBEfIh~v6MgmHp-Xchod1OfKFrxFrzHgcJbn7E8edTFjZA6JCi~DtFxFelQz1lSBd-QB1qJnA0g-pVL5qngNUojXJCXs4qWcQ7ICLpvIc-Fpfj-0F1gkVlGDSGkb1yLH3~8p4czYgR3W5D7OpwXzezz6clpV8kmbd~x2SotdWsXBPRhqpewO38coU4dJG3OEUbuYmdN~nJMfWbmlcM1lXzz2vBsys4sZzW6dV3hZnbvbfxNTqbdqOh-KXi1iAzXv7CVTun0ubw~CfeGpcAqutC5loRUq7Mq62ngOukyv8Z9AAAA
|
||||
*
|
||||
* Lists descriptions of all running jobs. The exact format of the
|
||||
* description depends on the type of job.
|
||||
* -------------------------------------------------
|
||||
* </pre>
|
||||
*/
|
||||
public class TunnelManager implements Runnable {
|
||||
private final static Log _log = new Log(TunnelManager.class);
|
||||
private I2PTunnel _tunnel;
|
||||
private ServerSocket _socket;
|
||||
private boolean _keepAccepting;
|
||||
|
||||
public TunnelManager(int listenPort) {
|
||||
this(null, listenPort);
|
||||
}
|
||||
public TunnelManager(String listenHost, int listenPort) {
|
||||
_tunnel = new I2PTunnel();
|
||||
_keepAccepting = true;
|
||||
try {
|
||||
if (listenHost != null) {
|
||||
_socket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
|
||||
_log.info("Listening for tunnel management clients on " + listenHost + ":" + listenPort);
|
||||
} else {
|
||||
_socket = new ServerSocket(listenPort);
|
||||
_log.info("Listening for tunnel management clients on localhost:" + listenPort);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting up tunnel management listener on " + listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
int port = 7676;
|
||||
String host = null;
|
||||
if (args.length == 1) {
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Usage: TunnelManager [host] [port]");
|
||||
return;
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
host = args[0];
|
||||
try {
|
||||
port = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Usage: TunnelManager [host] [port]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TunnelManager mgr = new TunnelManager(host, port);
|
||||
Thread t = new Thread(mgr, "Listener");
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (_socket == null) {
|
||||
_log.error("Unable to start listening, since the socket was not bound. Already running?");
|
||||
return;
|
||||
}
|
||||
_log.debug("Running");
|
||||
try {
|
||||
while (_keepAccepting) {
|
||||
Socket socket = _socket.accept();
|
||||
_log.debug("Client accepted");
|
||||
if (socket != null) {
|
||||
Thread t = new I2PThread(new TunnelManagerClientRunner(this, socket));
|
||||
t.setName("TunnelManager Client");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error accepting connections", ioe);
|
||||
} catch (Exception e) {
|
||||
_log.error("Other error?!", e);
|
||||
} finally {
|
||||
if (_socket != null) try { _socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
public void error(String msg, OutputStream out) throws IOException {
|
||||
out.write(msg.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void processQuit(OutputStream out) throws IOException {
|
||||
out.write("Nice try".getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void processList(OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
long startCommand = Clock.getInstance().now();
|
||||
_tunnel.runCommand("list", buf);
|
||||
Object obj = _tunnel.waitEventValue("listDone");
|
||||
long endCommand = Clock.getInstance().now();
|
||||
String str = buf.getBuffer();
|
||||
_log.debug("ListDone complete after " + (endCommand-startCommand) + "ms: [" + str + "]");
|
||||
out.write(str.getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processListenOn(String ip, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("listen_on " + ip, buf);
|
||||
String status = (String)_tunnel.waitEventValue("listen_onResult");
|
||||
out.write((status + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* "lookup <name>" returns with the result in base64, else "Unknown host" [or something like that],
|
||||
* then a newline.
|
||||
*
|
||||
*/
|
||||
public void processLookup(String name, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("lookup " + name, buf);
|
||||
String rv = (String)_tunnel.waitEventValue("lookupResult");
|
||||
out.write(rv.getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processTestDestination(String destKey, OutputStream out) throws IOException {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
out.write("valid\n".getBytes());
|
||||
} catch (DataFormatException dfe) {
|
||||
out.write("invalid\n".getBytes());
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void processConvertPrivate(String priv, OutputStream out) throws IOException {
|
||||
try {
|
||||
Destination dest = new Destination();
|
||||
dest.fromBase64(priv);
|
||||
String str = dest.toBase64();
|
||||
out.write(str.getBytes());
|
||||
out.write('\n');
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error converting private data", dfe);
|
||||
out.write("Error converting private key\n".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public void processClose(String which, boolean forced, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand((forced?"close forced ":"close ") + which, buf);
|
||||
String str = (String)_tunnel.waitEventValue("closeResult");
|
||||
out.write((str + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* "genkey" returns with the base64 of the destination, followed by a tab, then the base64 of that
|
||||
* destination's private keys, then a newline.
|
||||
*
|
||||
*/
|
||||
public void processGenKey(OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("gentextkeys", buf);
|
||||
String priv = (String)_tunnel.waitEventValue("privateKey");
|
||||
String pub = (String)_tunnel.waitEventValue("publicDestination");
|
||||
out.write((pub + "\t" + priv).getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
|
||||
Integer taskId = (Integer)_tunnel.waitEventValue("clientTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String)_tunnel.waitEventValue("openClientResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer)_tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue()
|
||||
+ "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenHTTPClient(int listenPort,
|
||||
String proxy,
|
||||
OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
|
||||
Integer taskId = (Integer)_tunnel.waitEventValue("httpclientTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String)_tunnel.waitEventValue("openHTTPClientResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer)_tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue()
|
||||
+ "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenSOCKSTunnel(int listenPort,
|
||||
OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("sockstunnel " + listenPort, buf);
|
||||
Integer taskId = (Integer)_tunnel.waitEventValue("sockstunnelTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String)_tunnel.waitEventValue("openSOCKSTunnelResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer)_tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue()
|
||||
+ "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenServer(String serverHost, int serverPort, String privateKeys, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("textserver " + serverHost + " " + serverPort + " " + privateKeys, buf);
|
||||
Integer taskId = (Integer)_tunnel.waitEventValue("serverTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
String rv = (String)_tunnel.waitEventValue("openServerResult");
|
||||
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frisbee.
|
||||
*
|
||||
*/
|
||||
public void unknownCommand(String command, OutputStream out) throws IOException {
|
||||
out.write("Unknown command: ".getBytes());
|
||||
out.write(command.getBytes());
|
||||
out.write("\n".getBytes());
|
||||
}
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Runner thread that reads commands from the socket and fires off commands to
|
||||
* the TunnelManager
|
||||
*
|
||||
*/
|
||||
class TunnelManagerClientRunner implements Runnable {
|
||||
private final static Log _log = new Log(TunnelManagerClientRunner.class);
|
||||
private TunnelManager _mgr;
|
||||
private Socket _clientSocket;
|
||||
|
||||
public TunnelManagerClientRunner(TunnelManager mgr, Socket socket) {
|
||||
_clientSocket = socket;
|
||||
_mgr = mgr;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_log.debug("Client running");
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(_clientSocket.getInputStream()));
|
||||
OutputStream out = _clientSocket.getOutputStream();
|
||||
|
||||
String cmd = reader.readLine();
|
||||
if (cmd != null)
|
||||
processCommand(cmd, out);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error processing client commands", ioe);
|
||||
} finally {
|
||||
if (_clientSocket != null) try { _clientSocket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
_log.debug("Client closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the command string and fire off the appropriate tunnelManager method,
|
||||
* sending the results to the output stream
|
||||
*/
|
||||
private void processCommand(String command, OutputStream out) throws IOException {
|
||||
_log.debug("Processing [" + command + "]");
|
||||
StringTokenizer tok = new StringTokenizer(command);
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.unknownCommand(command, out);
|
||||
} else {
|
||||
String cmd = tok.nextToken();
|
||||
if ("quit".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processQuit(out);
|
||||
} else if ("lookup".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processLookup(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: lookup <hostname>", out);
|
||||
} else if ("testdestination".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processTestDestination(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: testdestination <publicDestination>", out);
|
||||
} else if ("convertprivate".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processConvertPrivate(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: convertprivate <privateData>", out);
|
||||
} else if ("close".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
String closeArg;
|
||||
if ((closeArg = tok.nextToken()).equals("forced")) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.processClose(tok.nextToken(), true, out);
|
||||
} else {
|
||||
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
|
||||
}
|
||||
} else {
|
||||
_mgr.processClose(closeArg, false, out);
|
||||
}
|
||||
} else {
|
||||
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
|
||||
}
|
||||
} else if ("genkey".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processGenKey(out);
|
||||
} else if ("list".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processList(out);
|
||||
} else if ("listen_on".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.processListenOn(tok.nextToken(), out);
|
||||
} else {
|
||||
_mgr.error("Usage: listen_on <ip>", out);
|
||||
}
|
||||
} else if ("openclient".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String peer = null;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenPort> <peer>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenport> <peer>", out);
|
||||
return;
|
||||
}
|
||||
peer = tok.nextToken();
|
||||
_mgr.processOpenClient(listenPort, peer, out);
|
||||
} else if ("openhttpclient".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String proxy = "squid.i2p";
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openhttpclient <listenPort> [<proxy>]",out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
proxy = tok.nextToken();
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenport> [<proxy>]",out);
|
||||
return;
|
||||
}
|
||||
_mgr.processOpenHTTPClient(listenPort, proxy, out);
|
||||
} else if ("opensockstunnel".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: opensockstunnel <listenPort>",out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: opensockstunnel <listenport>",out);
|
||||
return;
|
||||
}
|
||||
_mgr.processOpenSOCKSTunnel(listenPort, out);
|
||||
} else if ("openserver".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String serverHost = null;
|
||||
String serverKeys = null;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
serverHost = tok.nextToken();
|
||||
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
serverKeys = tok.nextToken();
|
||||
_mgr.processOpenServer(serverHost, listenPort, serverKeys, out);
|
||||
} else {
|
||||
_mgr.unknownCommand(command, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnelClientBase;
|
||||
import net.i2p.i2ptunnel.I2PTunnelRunner;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
|
||||
private static final Log _log = new Log(I2PSOCKSTunnel.class);
|
||||
|
||||
protected Destination outProxyDest = null;
|
||||
|
||||
//public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest) {
|
||||
// I2PSOCKSTunnel(localPort, l, ownDest, (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest,
|
||||
EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "SOCKSHandler");
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openSOCKSTunnelResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> SOCKSTunnel");
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openSOCKSTunnelResult", "ok");
|
||||
}
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket();
|
||||
new I2PTunnelRunner (clientSock, destSock, sockLock, null);
|
||||
} catch (SOCKSException e) {
|
||||
_log.error("Error from SOCKS connection: " + e.getMessage());
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,309 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.HexDump;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/*
|
||||
* Class that manages SOCKS5 connections, and forwards them to
|
||||
* destination hosts or (eventually) some outproxy.
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
public class SOCKS5Server extends SOCKSServer {
|
||||
private static final Log _log = new Log(SOCKS5Server.class);
|
||||
|
||||
private static final int SOCKS_VERSION_5 = 0x05;
|
||||
|
||||
private Socket clientSock = null;
|
||||
|
||||
private boolean setupCompleted = false;
|
||||
|
||||
/**
|
||||
* Create a SOCKS5 server that communicates with the client using
|
||||
* the specified socket. This method should not be invoked
|
||||
* directly: new SOCKS5Server objects should be created by using
|
||||
* SOCKSServerFactory.createSOCSKServer(). It is assumed that the
|
||||
* SOCKS VER field has been stripped from the input stream of the
|
||||
* client socket.
|
||||
*
|
||||
* @param clientSock client socket
|
||||
*/
|
||||
public SOCKS5Server(Socket clientSock) {
|
||||
this.clientSock = clientSock;
|
||||
}
|
||||
|
||||
public Socket getClientSocket() throws SOCKSException {
|
||||
setupServer();
|
||||
|
||||
return clientSock;
|
||||
}
|
||||
|
||||
protected void setupServer() throws SOCKSException {
|
||||
if (setupCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataInputStream in;
|
||||
DataOutputStream out;
|
||||
try {
|
||||
in = new DataInputStream(clientSock.getInputStream());
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
init(in, out);
|
||||
manageRequest(in, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
|
||||
setupCompleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* SOCKS5 connection initialization. This method assumes that
|
||||
* SOCKS "VER" field has been stripped from the input stream.
|
||||
*/
|
||||
private void init (DataInputStream in,
|
||||
DataOutputStream out) throws IOException, SOCKSException {
|
||||
int nMethods = in.readByte() & 0xff;
|
||||
boolean methodOk = false;
|
||||
int method = Method.NO_ACCEPTABLE_METHODS;
|
||||
|
||||
for (int i = 0; i < nMethods; ++i) {
|
||||
method = in.readByte() & 0xff;
|
||||
if (method == Method.NO_AUTH_REQUIRED) {
|
||||
// That's fine, we do support this method
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean canContinue = false;
|
||||
switch (method) {
|
||||
case Method.NO_AUTH_REQUIRED:
|
||||
_log.debug("no authentication required");
|
||||
sendInitReply(Method.NO_AUTH_REQUIRED, out);
|
||||
return;
|
||||
default:
|
||||
_log.debug("no suitable authentication methods found ("
|
||||
+ Integer.toHexString(method)+ ")");
|
||||
sendInitReply(Method.NO_ACCEPTABLE_METHODS, out);
|
||||
throw new SOCKSException("Unsupported authentication method");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SOCKS5 request management. This method assumes that all the
|
||||
* stuff preceding or enveloping the actual request (e.g. protocol
|
||||
* initialization, integrity/confidentiality encapsulations, etc)
|
||||
* has been stripped out of the input/output streams.
|
||||
*/
|
||||
private void manageRequest(DataInputStream in,
|
||||
DataOutputStream out) throws IOException, SOCKSException {
|
||||
int socksVer = in.readByte() & 0xff;
|
||||
if (socksVer != SOCKS_VERSION_5) {
|
||||
_log.debug("error in SOCKS5 request (protocol != 5? wtf?)");
|
||||
throw new SOCKSException("Invalid protocol version in request");
|
||||
}
|
||||
|
||||
int command = in.readByte() & 0xff;
|
||||
switch (command) {
|
||||
case Command.CONNECT:
|
||||
break;
|
||||
case Command.BIND:
|
||||
_log.debug("BIND command is not supported!");
|
||||
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED,
|
||||
AddressType.DOMAINNAME, null,
|
||||
"0.0.0.0", 0, out);
|
||||
throw new SOCKSException("BIND command not supported");
|
||||
case Command.UDP_ASSOCIATE:
|
||||
_log.debug("UDP ASSOCIATE command is not supported!");
|
||||
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED,
|
||||
AddressType.DOMAINNAME, null,
|
||||
"0.0.0.0", 0, out);
|
||||
throw new SOCKSException("UDP ASSOCIATE command not supported");
|
||||
default:
|
||||
_log.debug("unknown command in request ("
|
||||
+ Integer.toHexString(command) + ")");
|
||||
throw new SOCKSException("Invalid command in request");
|
||||
}
|
||||
|
||||
{
|
||||
// Reserved byte, should be 0x00
|
||||
byte rsv = in.readByte();
|
||||
}
|
||||
|
||||
int addressType = in.readByte() & 0xff;
|
||||
switch (addressType) {
|
||||
case AddressType.IPV4:
|
||||
connHostName = new String("");
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int octet = in.readByte() & 0xff;
|
||||
connHostName += Integer.toString(octet);
|
||||
if (i != 3) {
|
||||
connHostName += ".";
|
||||
}
|
||||
}
|
||||
_log.warn("IPV4 address type in request: " + connHostName
|
||||
+ ". Is your client secure?");
|
||||
break;
|
||||
case AddressType.DOMAINNAME:
|
||||
{
|
||||
int addrLen = in.readByte() & 0xff;
|
||||
if (addrLen == 0) {
|
||||
_log.debug("0-sized address length? wtf?");
|
||||
throw new SOCKSException("Illegal DOMAINNAME length");
|
||||
}
|
||||
byte addr[] = new byte[addrLen];
|
||||
in.readFully(addr);
|
||||
connHostName = new String(addr);
|
||||
}
|
||||
_log.debug("DOMAINNAME address type in request: " + connHostName);
|
||||
break;
|
||||
case AddressType.IPV6:
|
||||
_log.warn("IP V6 address type in request! Is your client secure?"
|
||||
+ " (IPv6 is not supported, anyway :-)");
|
||||
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED,
|
||||
AddressType.DOMAINNAME, null,
|
||||
"0.0.0.0", 0, out);
|
||||
throw new SOCKSException("IPV6 addresses not supported");
|
||||
default:
|
||||
_log.debug("unknown address type in request ("
|
||||
+ Integer.toHexString(command) + ")");
|
||||
throw new SOCKSException("Invalid addresses type in request");
|
||||
}
|
||||
|
||||
connPort = in.readUnsignedShort();
|
||||
if (connPort == 0) {
|
||||
_log.debug("trying to connect to TCP port 0? Dropping!");
|
||||
throw new SOCKSException("Invalid port number in request");
|
||||
}
|
||||
}
|
||||
|
||||
protected void confirmConnection() throws SOCKSException {
|
||||
DataInputStream in;
|
||||
DataOutputStream out;
|
||||
try {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
sendRequestReply(Reply.SUCCEEDED,
|
||||
AddressType.IPV4,
|
||||
InetAddress.getByName("127.0.0.1"),
|
||||
null, 1, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified reply during SOCKS5 initialization
|
||||
*/
|
||||
private void sendInitReply(int replyCode,
|
||||
DataOutputStream out) throws IOException {
|
||||
ByteArrayOutputStream reps = new ByteArrayOutputStream();
|
||||
|
||||
reps.write(SOCKS_VERSION_5);
|
||||
reps.write(replyCode);
|
||||
|
||||
byte[] reply = reps.toByteArray();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Sending init reply:\n" + HexDump.dump(reply));
|
||||
}
|
||||
|
||||
out.write(reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified reply to a request of the client. Either
|
||||
* one of inetAddr or domainName can be null, depending on
|
||||
* addressType.
|
||||
*/
|
||||
private void sendRequestReply(int replyCode,
|
||||
int addressType,
|
||||
InetAddress inetAddr,
|
||||
String domainName,
|
||||
int bindPort,
|
||||
DataOutputStream out) throws IOException {
|
||||
ByteArrayOutputStream reps = new ByteArrayOutputStream();
|
||||
DataOutputStream dreps = new DataOutputStream(reps);
|
||||
|
||||
dreps.write(SOCKS_VERSION_5);
|
||||
dreps.write(replyCode);
|
||||
|
||||
// Reserved byte, should be 0x00
|
||||
dreps.write(0x00);
|
||||
|
||||
dreps.write(addressType);
|
||||
|
||||
switch (addressType) {
|
||||
case AddressType.IPV4:
|
||||
dreps.write(inetAddr.getAddress());
|
||||
break;
|
||||
case AddressType.DOMAINNAME:
|
||||
dreps.writeByte(domainName.length());
|
||||
dreps.writeBytes(domainName);
|
||||
break;
|
||||
default:
|
||||
_log.error("unknown address type passed to sendReply() ("
|
||||
+ Integer.toHexString(addressType) + ")! wtf?");
|
||||
return;
|
||||
}
|
||||
|
||||
dreps.writeShort(bindPort);
|
||||
|
||||
byte[] reply = reps.toByteArray();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Sending request reply:\n" + HexDump.dump(reply));
|
||||
}
|
||||
|
||||
out.write(reply);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Some namespaces to enclose SOCKS protocol codes
|
||||
*/
|
||||
private class Method {
|
||||
private static final int NO_AUTH_REQUIRED = 0x00;
|
||||
private static final int NO_ACCEPTABLE_METHODS = 0xff;
|
||||
}
|
||||
|
||||
private class AddressType {
|
||||
private static final int IPV4 = 0x01;
|
||||
private static final int DOMAINNAME = 0x03;
|
||||
private static final int IPV6 = 0x04;
|
||||
}
|
||||
|
||||
private class Command {
|
||||
private static final int CONNECT = 0x01;
|
||||
private static final int BIND = 0x02;
|
||||
private static final int UDP_ASSOCIATE = 0x03;
|
||||
}
|
||||
|
||||
private class Reply {
|
||||
private static final int SUCCEEDED = 0x00;
|
||||
private static final int GENERAL_SOCKS_SERVER_FAILURE = 0x01;
|
||||
private static final int CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02;
|
||||
private static final int NETWORK_UNREACHABLE = 0x03;
|
||||
private static final int HOST_UNREACHABLE = 0x04;
|
||||
private static final int CONNECTION_REFUSED = 0x05;
|
||||
private static final int TTL_EXPIRED = 0x06;
|
||||
private static final int COMMAND_NOT_SUPPORTED = 0x07;
|
||||
private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
/**
|
||||
* Exception thrown by socket methods
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
public class SOCKSException extends Exception {
|
||||
|
||||
public SOCKSException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SOCKSException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
100
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
Normal file
100
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
Normal file
@ -0,0 +1,100 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Abstract base class used by all SOCKS servers.
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
public abstract class SOCKSServer {
|
||||
private static final Log _log = new Log(SOCKSServer.class);
|
||||
|
||||
/* Details about the connection requested by client */
|
||||
protected String connHostName = null;
|
||||
protected int connPort = 0;
|
||||
|
||||
I2PSocket destSocket = null;
|
||||
|
||||
Object FIXME = new Object();
|
||||
|
||||
/**
|
||||
* Perform server initialization (expecially regarding protected
|
||||
* variables).
|
||||
*/
|
||||
protected abstract void setupServer() throws SOCKSException;
|
||||
|
||||
/**
|
||||
* Get a socket that can be used to send/receive 8-bit clean data
|
||||
* to/from the client.
|
||||
*
|
||||
* @return a Socket connected with the client
|
||||
*/
|
||||
public abstract Socket getClientSocket() throws SOCKSException;
|
||||
|
||||
|
||||
/**
|
||||
* Confirm to the client that the connection has succeeded
|
||||
*/
|
||||
protected abstract void confirmConnection() throws SOCKSException;
|
||||
|
||||
/**
|
||||
* Get an I2PSocket that can be used to send/receive 8-bit clean data
|
||||
* to/from the destination of the SOCKS connection.
|
||||
*
|
||||
* @return an I2PSocket connected with the destination
|
||||
*/
|
||||
public I2PSocket getDestinationI2PSocket() throws SOCKSException {
|
||||
setupServer();
|
||||
|
||||
if (connHostName == null) {
|
||||
_log.error("BUG: destination host name has not been initialized!");
|
||||
throw new SOCKSException("BUG! See the logs!");
|
||||
}
|
||||
if (connPort == 0) {
|
||||
_log.error("BUG: destination port has not been initialized!");
|
||||
throw new SOCKSException("BUG! See the logs!");
|
||||
}
|
||||
|
||||
// FIXME: here we should read our config file, select an
|
||||
// outproxy, and instantiate the proper socket class that
|
||||
// handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...).
|
||||
I2PSocket destSock;
|
||||
|
||||
try {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_log.debug("connecting to " + connHostName + "...");
|
||||
I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
destSock = sm.connect(I2PTunnel.destFromName(connHostName),
|
||||
new I2PSocketOptions());
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} else {
|
||||
_log.error("We don't support outproxies (yet)");
|
||||
throw new SOCKSException("Ouproxies not supported (yet)");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
throw new SOCKSException("Error in destination format");
|
||||
} catch (I2PException e) {
|
||||
throw new SOCKSException("I2P error (" + e.getMessage() + ")");
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Factory class for creating SOCKS forwarders through I2P
|
||||
*/
|
||||
public class SOCKSServerFactory {
|
||||
private final static Log _log = new Log(SOCKSServerFactory.class);
|
||||
|
||||
/**
|
||||
* Create a new SOCKS server, using the provided socket (that must
|
||||
* be connected to a client) to select the proper SOCKS protocol
|
||||
* version. This method wil strip the SOCKS VER field from the
|
||||
* provided sockets's input stream.
|
||||
*
|
||||
* @param s a Socket used to choose the SOCKS server type
|
||||
*/
|
||||
public static SOCKSServer createSOCKSServer(Socket s) throws SOCKSException {
|
||||
SOCKSServer serv;
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(s.getInputStream());
|
||||
int socksVer = in.readByte();
|
||||
|
||||
switch (socksVer) {
|
||||
case 0x05: // SOCKS version 5
|
||||
serv = new SOCKS5Server(s);
|
||||
break;
|
||||
default:
|
||||
_log.debug("SOCKS protocol version not supported ("
|
||||
+ Integer.toHexString(socksVer) + ")");
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
_log.debug("error reading SOCKS protocol version");
|
||||
throw new SOCKSException("Connection error ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
|
||||
return serv;
|
||||
}
|
||||
}
|
83
apps/ministreaming/doc/protocol.txt
Normal file
83
apps/ministreaming/doc/protocol.txt
Normal file
@ -0,0 +1,83 @@
|
||||
ministreaming protocol:
|
||||
*******************
|
||||
|
||||
Each message looks like the following
|
||||
|
||||
1 byte type
|
||||
3 byte id
|
||||
x byte payload.
|
||||
|
||||
messages from client to server have type 0xA0 to 0xAF,
|
||||
messages from server to client have type 0x50 to 0x5F.
|
||||
|
||||
Each "connections" has 2 IDs, a client ID and a server ID.
|
||||
These IDs may be any 3-byte values except 00 00 00, which is reserved.
|
||||
|
||||
All connections are created as PROP_RELIABILITY_GUARANTEED.
|
||||
|
||||
"actions" are the things a proper tunnel implementation SHOULD do
|
||||
when it receives such a message.
|
||||
|
||||
|
||||
Client->Server:
|
||||
===============
|
||||
|
||||
0xA0 Send data
|
||||
id: the server id
|
||||
payload: the data to send
|
||||
actions: send the data to the TCP connection
|
||||
|
||||
0xA1 SYN
|
||||
id: the client id
|
||||
payload: the public key dynamically created by the client
|
||||
actions: create a server ID and create a TCP connection. When successful,
|
||||
send an ACK back, otherwise a close.
|
||||
|
||||
0xA2 Close
|
||||
id: the server id
|
||||
payload: nothing
|
||||
actions: close the connection
|
||||
|
||||
Server->Client
|
||||
==============
|
||||
|
||||
0x50 Send data
|
||||
id: the client id
|
||||
payload: the data to send
|
||||
actions: send the data to the TCP connection
|
||||
|
||||
0x51 ACK
|
||||
id: the client id
|
||||
payload: the server id
|
||||
actions: nothing
|
||||
|
||||
0x52 Close
|
||||
id: the client id
|
||||
payload: nothing
|
||||
actions: close the connection
|
||||
|
||||
|
||||
Sample conversations:
|
||||
=====================
|
||||
|
||||
a) Service not available (e.g. the server on the TCP port is not running)
|
||||
|
||||
C->S A1 12 34 56 key... (SYN, client ID = 12 34 56)
|
||||
S->C 52 12 34 56 (Close)
|
||||
|
||||
b) Service available, server sends data, client closes
|
||||
|
||||
C->S A1 23 45 67 key... (SYN)
|
||||
S->C 51 23 45 67 98 76 54 (ACK, server ID = 98 76 54)
|
||||
S->C 50 23 45 67 data (Send data)
|
||||
C->S A2 98 76 54 (Close)
|
||||
|
||||
c) Service available, client sends data first, server closes after answer (HTTP)
|
||||
|
||||
C->S A1 11 11 11 key... (SYN)
|
||||
S->C 51 11 11 11 FF FF FF (ACK)
|
||||
C->S A0 FF FF FF data (send data)
|
||||
S->C 50 11 11 11 data (answer with data)
|
||||
S->C 50 11 11 11 data (more data)
|
||||
S->C 51 11 11 11 (Close)
|
||||
|
10
apps/ministreaming/doc/readme.license.txt
Normal file
10
apps/ministreaming/doc/readme.license.txt
Normal file
@ -0,0 +1,10 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/ministreaming module is the root of the
|
||||
ministreaming library, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/ministreaming module is released
|
||||
under a BSD license unless otherwise marked.
|
||||
Alternate licenses that may be used include Cryptix,
|
||||
MIT, as well as code granted into the public domain.
|
35
apps/ministreaming/java/build.xml
Normal file
35
apps/ministreaming/java/build.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="ministreaming">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" destdir="./build/obj" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/mstreaming.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2P ministreaming library" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
@ -0,0 +1,159 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
/** Like a StringBuffer, but for bytes */
|
||||
public class ByteCollector {
|
||||
byte[] contents;
|
||||
int size;
|
||||
|
||||
public ByteCollector() {
|
||||
contents=new byte[80];
|
||||
size=0;
|
||||
}
|
||||
|
||||
public ByteCollector(byte[] b) {
|
||||
this();
|
||||
append(b);
|
||||
}
|
||||
|
||||
public ByteCollector(byte b) {
|
||||
this();
|
||||
append(b);
|
||||
}
|
||||
|
||||
public ByteCollector append (byte b) {
|
||||
ensureCapacity(size+1);
|
||||
contents[size++]=b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ByteCollector append (byte[] b) {
|
||||
ensureCapacity(size+b.length);
|
||||
System.arraycopy(b,0,contents,size,b.length);
|
||||
size+=b.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ByteCollector append(byte[] b, int len) {
|
||||
return append(b,0,len);
|
||||
}
|
||||
|
||||
public ByteCollector append(byte[] b, int off, int len) {
|
||||
ensureCapacity(size+len);
|
||||
System.arraycopy(b,off,contents,size,len);
|
||||
size+=len;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ByteCollector append(ByteCollector bc) {
|
||||
// optimieren?
|
||||
return append(bc.toByteArray());
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
byte[] result=new byte[size];
|
||||
System.arraycopy(contents,0,result,0,size);
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] startToByteArray(int maxlen) {
|
||||
if (size < maxlen) {
|
||||
byte[] res = toByteArray();
|
||||
clear();
|
||||
return res;
|
||||
} else {
|
||||
byte[] result = new byte[maxlen];
|
||||
System.arraycopy(contents,0,result,0,maxlen);
|
||||
System.arraycopy(contents,maxlen,contents,0,size-maxlen);
|
||||
size-=maxlen;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean ensureCapacity(int cap) {
|
||||
if (contents.length<cap) {
|
||||
int l=contents.length;
|
||||
while (l<cap) {
|
||||
l=(l*3)/2+1;
|
||||
}
|
||||
byte[] newcont=new byte[l];
|
||||
System.arraycopy(contents,0,newcont,0,size);
|
||||
contents=newcont;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return size==0;
|
||||
}
|
||||
|
||||
public int indexOf(ByteCollector bc) {
|
||||
// optimieren?
|
||||
return indexOf(bc.toByteArray());
|
||||
}
|
||||
|
||||
public int indexOf(byte b) {
|
||||
// optimieren?
|
||||
return indexOf(new byte[] {b});
|
||||
}
|
||||
|
||||
public int indexOf(byte[] ba) {
|
||||
loop:
|
||||
for (int i=0;i<size-ba.length+1;i++) {
|
||||
for (int j=0;j<ba.length;j++) {
|
||||
if (contents[i+j]!=ba[j]) continue loop;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
size=0;
|
||||
}
|
||||
|
||||
public void clearAndShorten() {
|
||||
size=0;
|
||||
contents=new byte[80];
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new String(toByteArray());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int h =0;
|
||||
for (int i=0;i<size;i++) {
|
||||
h+=contents[i]*contents[i];
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof ByteCollector) {
|
||||
ByteCollector by=(ByteCollector)o;
|
||||
if (size!=by.size) return false;
|
||||
for (int i=0;i<size;i++) {
|
||||
if (contents[i]!=by.contents[i]) return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public byte removeFirst() {
|
||||
byte bb=contents[0];
|
||||
if (size==0)
|
||||
throw new IllegalArgumentException("ByteCollector is empty");
|
||||
if(size>1)
|
||||
System.arraycopy(contents,1,contents,0,--size);
|
||||
else
|
||||
size=0;
|
||||
return bb;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
|
||||
/**
|
||||
* Defines how to listen for streaming peer connections
|
||||
*
|
||||
*/
|
||||
public interface I2PServerSocket {
|
||||
/**
|
||||
* Closes the socket.
|
||||
*/
|
||||
public void close() throws I2PException;
|
||||
|
||||
/**
|
||||
* Waits for the next socket connecting. If a remote user tried to make a
|
||||
* connection and the local application wasn't .accept()ing new connections,
|
||||
* they should get refused (if .accept() doesnt occur in some small period)
|
||||
*
|
||||
* @throws I2PException if there is a problem with reading a new socket
|
||||
* from the data available (aka the I2PSession closed, etc)
|
||||
*/
|
||||
public I2PSocket accept() throws I2PException;
|
||||
|
||||
/**
|
||||
* Access the manager which is coordinating the server socket
|
||||
*/
|
||||
public I2PSocketManager getManager();
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Initial stub implementation for the server socket
|
||||
*
|
||||
*/
|
||||
class I2PServerSocketImpl implements I2PServerSocket {
|
||||
private final static Log _log = new Log(I2PServerSocketImpl.class);
|
||||
private I2PSocketManager mgr;
|
||||
private I2PSocket cached=null; // buffer one socket here
|
||||
|
||||
public I2PServerSocketImpl(I2PSocketManager mgr) {
|
||||
this.mgr = mgr;
|
||||
}
|
||||
|
||||
public synchronized I2PSocket accept() throws I2PException {
|
||||
while(cached == null) {
|
||||
myWait();
|
||||
}
|
||||
I2PSocket ret=cached;
|
||||
cached=null;
|
||||
notifyAll();
|
||||
_log.debug("TIMING: handed out accept result "+ret.hashCode());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public synchronized boolean getNewSocket(I2PSocket s){
|
||||
while(cached != null) {
|
||||
myWait();
|
||||
}
|
||||
cached=s;
|
||||
notifyAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close() throws I2PException {
|
||||
//noop
|
||||
}
|
||||
|
||||
private void myWait() {
|
||||
try{
|
||||
wait();
|
||||
} catch (InterruptedException ex) {}
|
||||
}
|
||||
|
||||
public I2PSocketManager getManager() { return mgr; }
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Minimalistic adapter between the socket api and I2PTunnel's way.
|
||||
* Note that this interface is a "subinterface" of the interface
|
||||
* defined in the "official" streaming api.
|
||||
*/
|
||||
public interface I2PSocket {
|
||||
/**
|
||||
* Return the Destination of this side of the socket.
|
||||
*/
|
||||
public Destination getThisDestination();
|
||||
|
||||
/**
|
||||
* Return the destination of the peer.
|
||||
*/
|
||||
public Destination getPeerDestination();
|
||||
|
||||
/**
|
||||
* Return an InputStream to read from the socket.
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Return an OutputStream to write into the socket.
|
||||
*/
|
||||
public OutputStream getOutputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the socket if not closed yet
|
||||
*/
|
||||
public void close() throws IOException;
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Initial stub implementation for the socket
|
||||
*
|
||||
*/
|
||||
class I2PSocketImpl implements I2PSocket {
|
||||
private final static Log _log = new Log(I2PSocketImpl.class);
|
||||
|
||||
public static final int MAX_PACKET_SIZE = 1024*32;
|
||||
public static final int PACKET_DELAY=100;
|
||||
|
||||
private I2PSocketManager manager;
|
||||
private Destination local;
|
||||
private Destination remote;
|
||||
private String localID;
|
||||
private String remoteID;
|
||||
private Object remoteIDWaiter = new Object();
|
||||
private I2PInputStream in;
|
||||
private I2POutputStream out;
|
||||
private boolean outgoing;
|
||||
private Object flagLock = new Object();
|
||||
private boolean closed = false, sendClose=true, closed2=false;
|
||||
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManager mgr,
|
||||
boolean outgoing, String localID) {
|
||||
this.outgoing=outgoing;
|
||||
manager = mgr;
|
||||
remote = peer;
|
||||
local = mgr.getSession().getMyDestination();
|
||||
in = new I2PInputStream();
|
||||
I2PInputStream pin = new I2PInputStream();
|
||||
out = new I2POutputStream(pin);
|
||||
new I2PSocketRunner(pin);
|
||||
this.localID = localID;
|
||||
}
|
||||
|
||||
public String getLocalID() {
|
||||
return localID;
|
||||
}
|
||||
|
||||
public void setRemoteID(String id) {
|
||||
synchronized(remoteIDWaiter) {
|
||||
remoteID=id;
|
||||
remoteIDWaiter.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteID(boolean wait) throws InterruptedIOException {
|
||||
return getRemoteID(wait, -1);
|
||||
}
|
||||
public String getRemoteID(boolean wait, long maxWait) throws InterruptedIOException {
|
||||
long dieAfter = System.currentTimeMillis() + maxWait;
|
||||
synchronized(remoteIDWaiter) {
|
||||
while (wait && remoteID==null) {
|
||||
try {
|
||||
if (maxWait > 0)
|
||||
remoteIDWaiter.wait(maxWait);
|
||||
else
|
||||
remoteIDWaiter.wait();
|
||||
} catch (InterruptedException ex) {}
|
||||
|
||||
if ( (maxWait > 0) && (System.currentTimeMillis() > dieAfter) )
|
||||
throw new InterruptedIOException("Timed out waiting for remote ID");
|
||||
}
|
||||
if (wait) {
|
||||
_log.debug("TIMING: RemoteID set to " + I2PSocketManager.getReadableForm(remoteID) +" for "+this.hashCode());
|
||||
}
|
||||
return remoteID;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteID() throws InterruptedIOException {
|
||||
return getRemoteID(false);
|
||||
}
|
||||
|
||||
public void queueData(byte[] data) {
|
||||
in.queueData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Destination of this side of the socket.
|
||||
*/
|
||||
public Destination getThisDestination() { return local; }
|
||||
|
||||
/**
|
||||
* Return the destination of the peer.
|
||||
*/
|
||||
public Destination getPeerDestination() { return remote; }
|
||||
|
||||
/**
|
||||
* Return an InputStream to read from the socket.
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException {
|
||||
if ( (in == null) )
|
||||
throw new IOException("Not connected");
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an OutputStream to write into the socket.
|
||||
*/
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
if ( (out == null) )
|
||||
throw new IOException("Not connected");
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket if not closed yet
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
synchronized(flagLock) {
|
||||
_log.debug("Closing connection");
|
||||
closed=true;
|
||||
}
|
||||
out.close();
|
||||
in.notifyClosed();
|
||||
}
|
||||
|
||||
public void internalClose() {
|
||||
synchronized(flagLock) {
|
||||
closed=true;
|
||||
closed2=true;
|
||||
sendClose=false;
|
||||
}
|
||||
out.close();
|
||||
in.notifyClosed();
|
||||
}
|
||||
|
||||
|
||||
private byte getMask(int add) {
|
||||
return (byte)((outgoing?(byte)0xA0:(byte)0x50)+(byte)add);
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
public class I2PInputStream extends InputStream {
|
||||
|
||||
private ByteCollector bc = new ByteCollector();
|
||||
|
||||
public int read() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
int res = read(b);
|
||||
if (res == 1) return b[0] & 0xff;
|
||||
if (res == -1) return -1;
|
||||
throw new RuntimeException("Incorrect read() result");
|
||||
}
|
||||
|
||||
public synchronized int read(byte[] b, int off, int len) throws IOException {
|
||||
_log.debug("Read called: "+this.hashCode());
|
||||
if (len==0) return 0;
|
||||
byte[] read = bc.startToByteArray(len);
|
||||
while (read.length==0) {
|
||||
synchronized(flagLock) {
|
||||
if (closed){
|
||||
_log.debug("Closed is set, so closing stream: "+this.hashCode());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {}
|
||||
read = bc.startToByteArray(len);
|
||||
}
|
||||
if (read.length>len) throw new RuntimeException("BUG");
|
||||
System.arraycopy(read,0,b,off,read.length);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Read from I2PInputStream " + this.hashCode()
|
||||
+ " returned "+read.length+" bytes");
|
||||
}
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
// _log.debug("Read from I2PInputStream " + this.hashCode()
|
||||
// + " returned "+read.length+" bytes:\n"
|
||||
// + HexDump.dump(read));
|
||||
//}
|
||||
return read.length;
|
||||
}
|
||||
|
||||
public int available() {
|
||||
return bc.getCurrentSize();
|
||||
}
|
||||
|
||||
public void queueData(byte[] data) {
|
||||
queueData(data,0,data.length);
|
||||
}
|
||||
|
||||
public synchronized void queueData(byte[] data, int off, int len) {
|
||||
_log.debug("Insert "+len+" bytes into queue: "+this.hashCode());
|
||||
bc.append(data, off, len);
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized void notifyClosed() {
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class I2POutputStream extends OutputStream {
|
||||
|
||||
public I2PInputStream sendTo;
|
||||
|
||||
public I2POutputStream(I2PInputStream sendTo) {
|
||||
this.sendTo=sendTo;
|
||||
}
|
||||
public void write(int b) throws IOException {
|
||||
write(new byte[] {(byte)b});
|
||||
}
|
||||
|
||||
public void write (byte[] b, int off, int len) throws IOException {
|
||||
sendTo.queueData(b,off,len);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
sendTo.notifyClosed();
|
||||
}
|
||||
}
|
||||
|
||||
public class I2PSocketRunner extends Thread {
|
||||
|
||||
public InputStream in;
|
||||
|
||||
public I2PSocketRunner(InputStream in) {
|
||||
_log.debug("Runner's input stream is: "+in.hashCode());
|
||||
this.in=in;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buffer = new byte[MAX_PACKET_SIZE];
|
||||
ByteCollector bc = new ByteCollector();
|
||||
boolean sent = true;
|
||||
try {
|
||||
int len, bcsize;
|
||||
// try {
|
||||
while (true) {
|
||||
len = in.read(buffer);
|
||||
bcsize = bc.getCurrentSize();
|
||||
if (len != -1) {
|
||||
bc.append(buffer,len);
|
||||
} else if (bcsize == 0) {
|
||||
break;
|
||||
}
|
||||
if ((bcsize < MAX_PACKET_SIZE)
|
||||
&& (in.available()==0)) {
|
||||
_log.debug("Runner Point d: "+this.hashCode());
|
||||
|
||||
try {
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ((bcsize >= MAX_PACKET_SIZE)
|
||||
|| (in.available()==0) ) {
|
||||
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
|
||||
if (data.length > 0) {
|
||||
_log.debug("Message size is: "+data.length);
|
||||
sent = sendBlock(data);
|
||||
if (!sent) {
|
||||
_log.error("Error sending message to peer. Killing socket runner");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((bc.getCurrentSize() > 0) && sent) {
|
||||
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! "
|
||||
+ "(input stream: " + in.hashCode() + "; "
|
||||
+ "queue size: " + bc.getCurrentSize() + ")");
|
||||
}
|
||||
synchronized(flagLock) {
|
||||
closed2=true;
|
||||
}
|
||||
// } catch (IOException ex) {
|
||||
// if (_log.shouldLog(Log.INFO))
|
||||
// _log.info("Error reading and writing", ex);
|
||||
// }
|
||||
boolean sc;
|
||||
synchronized(flagLock) {
|
||||
sc=sendClose;
|
||||
} // FIXME: Race here?
|
||||
if (sc) {
|
||||
_log.info("Sending close packet: "+outgoing);
|
||||
byte[] packet = I2PSocketManager.makePacket
|
||||
((byte)(getMask(0x02)),remoteID, new byte[0]);
|
||||
synchronized(manager.getSession()) {
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
}
|
||||
if (!sent) {
|
||||
_log.error("Error sending close packet to peer");
|
||||
}
|
||||
}
|
||||
manager.removeSocket(I2PSocketImpl.this);
|
||||
} catch (IOException ex) {
|
||||
// WHOEVER removes this event on inconsistent
|
||||
// state before fixing the inconsistent state (a
|
||||
// reference on the socket in the socket manager
|
||||
// etc.) will get hanged by me personally -- mihi
|
||||
_log.error("Error running - **INCONSISTENT STATE!!!**", ex);
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error running - **INCONSISTENT STATE!!!**" , ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendBlock(byte data[]) throws I2PSessionException {
|
||||
_log.debug("TIMING: Block to send for "+I2PSocketImpl.this.hashCode());
|
||||
if (remoteID==null) {
|
||||
_log.error("NULL REMOTEID");
|
||||
return false;
|
||||
}
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x00), remoteID,
|
||||
data);
|
||||
boolean sent;
|
||||
synchronized(flagLock) {
|
||||
if (closed2) return false;
|
||||
}
|
||||
synchronized(manager.getSession()) {
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* licensed under BSD license...
|
||||
* (if you know the proper clause for that, add it ...)
|
||||
*/
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Centralize the coordination and multiplexing of the local client's streaming.
|
||||
* There should be one I2PSocketManager for each I2PSession, and if an application
|
||||
* is sending and receiving data through the streaming library using an
|
||||
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManager implements I2PSessionListener {
|
||||
private final static Log _log = new Log(I2PSocketManager.class);
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
|
||||
public static final int PUBKEY_LENGTH=387;
|
||||
|
||||
|
||||
public I2PSocketManager() {
|
||||
_session=null;
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null)
|
||||
session.setSessionListener(this);
|
||||
}
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.error("Disconnected from the session");
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error("Error occurred: [" + message + "]", error);
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
if (msg.length == 1 && msg[0] == -1) {
|
||||
_log.debug("Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length <4) {
|
||||
_log.error("==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = new String(new byte[] {msg[1], msg[2], msg[3]},
|
||||
"ISO-8859-1");
|
||||
byte[] payload = new byte[msg.length-4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
_log.debug("Message read: type = [" + Integer.toHexString(type) +
|
||||
"] id = [" + getReadableForm(id)+
|
||||
"] payload length: " + payload.length + "]");
|
||||
synchronized(lock) {
|
||||
switch(type) {
|
||||
case 0x51: // ACK outgoing
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s == null) {
|
||||
_log.warn("No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
if (payload.length==3 && s.getRemoteID(false)==null) {
|
||||
String newID = new String(payload,
|
||||
"ISO-8859-1");
|
||||
s.setRemoteID(newID);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length != 3)
|
||||
_log.warn("Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn("Remote ID already exists? " + s.getRemoteID());
|
||||
return;
|
||||
}
|
||||
case 0x52: // disconnect outgoing
|
||||
_log.debug("*Disconnect outgoing!");
|
||||
try {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (payload.length==0 && s != null) {
|
||||
s.internalClose();
|
||||
_outSockets.remove(id);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length > 0)
|
||||
_log.warn("Disconnect packet had " + payload.length + " bytes");
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
}
|
||||
case 0x50: // packet send outgoing
|
||||
_log.debug("*Packet send outgoing [" + payload.length + "]");
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
case 0xA1: // SYN incoming
|
||||
_log.debug("*Syn!");
|
||||
if (payload.length==PUBKEY_LENGTH) {
|
||||
String newLocalID = makeID(_inSockets);
|
||||
Destination d = new Destination();
|
||||
d.readBytes(new ByteArrayInputStream(payload));
|
||||
|
||||
s = new I2PSocketImpl(d, this, false,
|
||||
newLocalID);
|
||||
s.setRemoteID(id);
|
||||
if (_serverSocket.getNewSocket(s)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket
|
||||
((byte)0x51, id,
|
||||
newLocalID.getBytes("ISO-8859-1"));
|
||||
boolean replySentOk = false;
|
||||
synchronized(_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending reply to " +
|
||||
d.calculateHash().toBase64() +
|
||||
" in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
}
|
||||
} else {
|
||||
byte[] packet =
|
||||
(" "+id).getBytes("ISO-8859-1");
|
||||
packet[0]=0x52;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.error("Error sending NACK for session creation");
|
||||
}
|
||||
s.internalClose();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
_log.error("Syn packet that has a payload not equal to the pubkey length (" + payload.length + " != " + PUBKEY_LENGTH + ")");
|
||||
return;
|
||||
}
|
||||
case 0xA2: // disconnect incoming
|
||||
_log.debug("*Disconnect incoming!");
|
||||
try {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length==0 && s != null) {
|
||||
s.internalClose();
|
||||
_inSockets.remove(id);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length > 0)
|
||||
_log.warn("Disconnect packet had " + payload.length + " bytes");
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
case 0xA0: // packet send incoming
|
||||
_log.debug("*Packet send incoming [" + payload.length + "]");
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
case 0xFF: // ignore
|
||||
return;
|
||||
}
|
||||
_log.error("\n\n=============== Unknown packet! "+
|
||||
"============"+
|
||||
"\nType: "+(int)type+
|
||||
"\nID: " + getReadableForm(id)+
|
||||
"\nBase64'ed Data: "+Base64.encode(payload)+
|
||||
"\n\n\n");
|
||||
if (id != null) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.error("Error processing", ise);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error processing", ioe);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug("Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error("Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) { _defaultOptions = options; }
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() { return _defaultOptions ; }
|
||||
|
||||
public I2PServerSocket getServerSocket() { return _serverSocket; }
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @throws I2PException if there is a problem connecting
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options) throws I2PException {
|
||||
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized(lock) {
|
||||
localID=makeID(_outSockets);
|
||||
lcID=getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(s.getLocalID(),s);
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte)0xA1, localID,
|
||||
pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
synchronized(_session) {
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
}
|
||||
if (!sent) {
|
||||
_log.info("Unable to send & receive ack for SYN packet");
|
||||
synchronized(lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
throw new I2PException("Unable to reach peer");
|
||||
}
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
if ("".equals(remoteID)) {
|
||||
throw new I2PException("Unable to reach peer");
|
||||
}
|
||||
_log.debug("TIMING: s given out for remoteID "+getReadableForm(remoteID));
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
_log.error("Timeout waiting for ack from syn for id " + getReadableForm(lcID), ioe);
|
||||
synchronized(lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
throw new I2PException("Timeout waiting for ack");
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
synchronized(lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
throw new I2PException("IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
synchronized(lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocket connect(Destination peer) throws I2PException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
|
||||
*
|
||||
*/
|
||||
public Set listSockets() {
|
||||
Set sockets = new HashSet(8);
|
||||
synchronized (lock) {
|
||||
sockets.addAll(_inSockets.values());
|
||||
sockets.addAll(_outSockets.values());
|
||||
}
|
||||
return sockets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the specified peer, returning true if they replied to the ping within
|
||||
* the timeout specified, false otherwise. This call blocks.
|
||||
*
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] {(byte)0xFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.error("I2PException:",ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
synchronized(lock) {
|
||||
_inSockets.remove(sock.getLocalID());
|
||||
_outSockets.remove(sock.getLocalID());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
try {
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(id.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new part the connection ID that is locally unique
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
public static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
try {
|
||||
do {
|
||||
int id = (int)(Math.random()*16777215+1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0]=(byte)(id / 65536);
|
||||
nid[1] = (byte)((id/256) % 256);
|
||||
nid[2]= (byte)(id %256);
|
||||
newID = new String(nid, "ISO-8859-1");
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet of the given type for the specified connection containing
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
try {
|
||||
byte[] packet = new byte[payload.length+4];
|
||||
packet[0]=type;
|
||||
byte[] temp = id.getBytes("ISO-8859-1");
|
||||
if (temp.length != 3)
|
||||
throw new RuntimeException("Incorrect ID length: "+
|
||||
temp.length);
|
||||
System.arraycopy(temp,0,packet,1,3);
|
||||
System.arraycopy(payload,0,packet,4,payload.length);
|
||||
return packet;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error building the packet", ex);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Simplify the creation of I2PSession and transient I2P Destination objects if
|
||||
* necessary to create a socket manager. This class is most likely how classes
|
||||
* will begin their use of the socket library
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManagerFactory {
|
||||
private final static Log _log = new Log(I2PSocketManagerFactory.class);
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager() {
|
||||
return createManager("localhost", 7654, new Properties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the given machine reachable through the given port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(String i2cpHost, int i2cpPort, Properties opts) {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream keyStream = new ByteArrayOutputStream(512);
|
||||
try {
|
||||
Destination dest = client.createDestination(keyStream);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(keyStream.toByteArray());
|
||||
return createManager(in, i2cpHost, i2cpPort, opts);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating the destination for socket manager", ioe);
|
||||
return null;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error creating the destination for socket manager", ie);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the I2CP router on the specified machine on the given
|
||||
* port
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort, Properties opts) {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, ""+i2cpPort);
|
||||
try {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
session.connect();
|
||||
return createManager(session);
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error creating session for socket manager", ise);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static I2PSocketManager createManager(I2PSession session) {
|
||||
I2PSocketManager mgr = new I2PSocketManager();
|
||||
mgr.setSession(session);
|
||||
return mgr;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
* No options available...
|
||||
*
|
||||
*/
|
||||
public class I2PSocketOptions {
|
||||
private long _connectTimeout;
|
||||
public I2PSocketOptions() {
|
||||
_connectTimeout = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
* @return milliseconds to wait, or -1 if we will wait indefinitely
|
||||
*/
|
||||
public long getConnectTimeout() { return _connectTimeout; }
|
||||
public void setConnectTimeout(long ms) { _connectTimeout = ms; }
|
||||
}
|
10
apps/phttprelay/doc/readme.license.txt
Normal file
10
apps/phttprelay/doc/readme.license.txt
Normal file
@ -0,0 +1,10 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/phttprelay module is the root of the
|
||||
PHTTPRelay application, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/phttprelay module is released
|
||||
into the public domain unless otherwise marked.
|
||||
Alternate licenses that may be used include BSD,
|
||||
Cryptix, and MIT.
|
43
apps/phttprelay/java/build.xml
Normal file
43
apps/phttprelay/java/build.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="phttprelay">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" destdir="./build/obj" classpath="../../../core/java/build/i2p.jar:lib/javax.servlet.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<war destfile="./build/phttprelay.war" webxml="web.xml">
|
||||
<classes dir="./build/obj/">
|
||||
<include name="**/*.class" />
|
||||
</classes>
|
||||
<lib dir="../../../core/java/build/">
|
||||
<include name="i2p.jar" />
|
||||
</lib>
|
||||
</war>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
|
||||
classpath="./lib/javax.servlet.jar"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2P phttp relay" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
159
apps/phttprelay/java/lib/LICENSE.html
Normal file
159
apps/phttprelay/java/lib/LICENSE.html
Normal file
@ -0,0 +1,159 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Jetty License</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="#FFFFFF">
|
||||
<FONT FACE=ARIAL,HELVETICA>
|
||||
<CENTER><FONT SIZE=+3><B>Jetty License</B></FONT></CENTER>
|
||||
<CENTER><FONT SIZE=-1><B>$Revision: 1.1 $</B></FONT></CENTER>
|
||||
|
||||
<B>Preamble:</B><p>
|
||||
|
||||
The intent of this document is to state the conditions under which the
|
||||
Jetty Package may be copied, such that the Copyright Holder maintains some
|
||||
semblance of control over the development of the package, while giving the
|
||||
users of the package the right to use, distribute and make reasonable
|
||||
modifications to the Package in accordance with the goals and ideals of
|
||||
the Open Source concept as described at
|
||||
<A HREF="http://www.opensource.org">http://www.opensource.org</A>.
|
||||
<P>
|
||||
It is the intent of this license to allow commercial usage of the Jetty
|
||||
package, so long as the source code is distributed or suitable visible
|
||||
credit given or other arrangements made with the copyright holders.
|
||||
|
||||
<P><B>Definitions:</B><P>
|
||||
|
||||
<UL>
|
||||
<LI> "Jetty" refers to the collection of Java classes that are
|
||||
distributed as a HTTP server with servlet capabilities and
|
||||
associated utilities.<p>
|
||||
|
||||
<LI> "Package" refers to the collection of files distributed by the
|
||||
Copyright Holder, and derivatives of that collection of files
|
||||
created through textual modification.<P>
|
||||
|
||||
<LI> "Standard Version" refers to such a Package if it has not been
|
||||
modified, or has been modified in accordance with the wishes
|
||||
of the Copyright Holder.<P>
|
||||
|
||||
<LI> "Copyright Holder" is whoever is named in the copyright or
|
||||
copyrights for the package. <BR>
|
||||
Mort Bay Consulting Pty. Ltd. (Australia) is the "Copyright
|
||||
Holder" for the Jetty package.<P>
|
||||
|
||||
<LI> "You" is you, if you're thinking about copying or distributing
|
||||
this Package.<P>
|
||||
|
||||
<LI> "Reasonable copying fee" is whatever you can justify on the
|
||||
basis of media cost, duplication charges, time of people involved,
|
||||
and so on. (You will not be required to justify it to the
|
||||
Copyright Holder, but only to the computing community at large
|
||||
as a market that must bear the fee.)<P>
|
||||
|
||||
<LI> "Freely Available" means that no fee is charged for the item
|
||||
itself, though there may be fees involved in handling the item.
|
||||
It also means that recipients of the item may redistribute it
|
||||
under the same conditions they received it.<P>
|
||||
</UL>
|
||||
|
||||
0. The Jetty Package is Copyright (c) Mort Bay Consulting Pty. Ltd.
|
||||
(Australia) and others. Individual files in this package may contain
|
||||
additional copyright notices. The javax.servlet packages are copyright
|
||||
Sun Microsystems Inc. <P>
|
||||
|
||||
1. The Standard Version of the Jetty package is
|
||||
available from <A HREF=http://jetty.mortbay.org>http://jetty.mortbay.org</A>.<P>
|
||||
|
||||
2. You may make and distribute verbatim copies of the source form
|
||||
of the Standard Version of this Package without restriction, provided that
|
||||
you include this license and all of the original copyright notices
|
||||
and associated disclaimers.<P>
|
||||
|
||||
3. You may make and distribute verbatim copies of the compiled form of the
|
||||
Standard Version of this Package without restriction, provided that you
|
||||
include this license.<P>
|
||||
|
||||
4. You may apply bug fixes, portability fixes and other modifications
|
||||
derived from the Public Domain or from the Copyright Holder. A Package
|
||||
modified in such a way shall still be considered the Standard Version.<P>
|
||||
|
||||
5. You may otherwise modify your copy of this Package in any way, provided
|
||||
that you insert a prominent notice in each changed file stating how and
|
||||
when you changed that file, and provided that you do at least ONE of the
|
||||
following:<P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
a) Place your modifications in the Public Domain or otherwise make them
|
||||
Freely Available, such as by posting said modifications to Usenet or
|
||||
an equivalent medium, or placing the modifications on a major archive
|
||||
site such as ftp.uu.net, or by allowing the Copyright Holder to include
|
||||
your modifications in the Standard Version of the Package.<P>
|
||||
|
||||
b) Use the modified Package only within your corporation or organization.<P>
|
||||
|
||||
c) Rename any non-standard classes so the names do not conflict
|
||||
with standard classes, which must also be provided, and provide
|
||||
a separate manual page for each non-standard class that clearly
|
||||
documents how it differs from the Standard Version.<P>
|
||||
|
||||
d) Make other arrangements with the Copyright Holder.<P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
6. You may distribute modifications or subsets of this Package in source
|
||||
code or compiled form, provided that you do at least ONE of the following:<P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
a) Distribute this license and all original copyright messages, together
|
||||
with instructions (in the about dialog, manual page or equivalent) on where
|
||||
to get the complete Standard Version.<P>
|
||||
|
||||
b) Accompany the distribution with the machine-readable source of
|
||||
the Package with your modifications. The modified package must include
|
||||
this license and all of the original copyright notices and associated
|
||||
disclaimers, together with instructions on where to get the complete
|
||||
Standard Version.<P>
|
||||
|
||||
c) Make other arrangements with the Copyright Holder.<P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
7. You may charge a reasonable copying fee for any distribution of this
|
||||
Package. You may charge any fee you choose for support of this Package.
|
||||
You may not charge a fee for this Package itself. However,
|
||||
you may distribute this Package in aggregate with other (possibly
|
||||
commercial) programs as part of a larger (possibly commercial) software
|
||||
distribution provided that you meet the other distribution requirements
|
||||
of this license.<P>
|
||||
|
||||
8. Input to or the output produced from the programs of this Package
|
||||
do not automatically fall under the copyright of this Package, but
|
||||
belong to whomever generated them, and may be sold commercially, and
|
||||
may be aggregated with this Package.<P>
|
||||
|
||||
9. Any program subroutines supplied by you and linked into this Package
|
||||
shall not be considered part of this Package.<P>
|
||||
|
||||
10. The name of the Copyright Holder may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.<P>
|
||||
|
||||
11. This license may change with each release of a Standard Version of
|
||||
the Package. You may choose to use the license associated with version
|
||||
you are using or the license of the latest Standard Version.<P>
|
||||
|
||||
12. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.<P>
|
||||
|
||||
13. If any superior law implies a warranty, the sole remedy under such shall
|
||||
be , at the Copyright Holders option either a) return of any price paid or
|
||||
b) use or reasonable endeavours to repair or replace the software.<P>
|
||||
|
||||
14. This license shall be read under the laws of Australia. <P>
|
||||
|
||||
<center>The End</center>
|
||||
|
||||
<center><FONT size=-1>This license was derived from the <I>Artistic</I> license published
|
||||
on <a href=http://www.opensource.org>http://www.opensource.com</a></font></center>
|
||||
</FONT>
|
||||
|
||||
|
6
apps/phttprelay/java/lib/readme.txt
Normal file
6
apps/phttprelay/java/lib/readme.txt
Normal file
@ -0,0 +1,6 @@
|
||||
The file javax.servlet.jar is distributed under the terms of LICENSE.html,
|
||||
which is the implementation of the java servlet classes as retrieved from
|
||||
http://jetty.mortbay.org/jetty/
|
||||
|
||||
It is only included to assist in building the phttprelay.war file on hosts
|
||||
that do not have a servlet container / implementation.
|
@ -0,0 +1,113 @@
|
||||
package net.i2p.phttprelay;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Check the status of previous message delivery, returning either pending or
|
||||
* unknown, where pending means that particular message ID for that particular
|
||||
* target is still on the server, and unknown means it has either not been created
|
||||
* or it has been sent successfully. It does this by sending HTTP 204 (NO CONTENT)
|
||||
* for pending, and HTTP 404 (NOT FOUND) for unknown. <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>CheckSendStatus</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.CheckSendStatusServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>CheckSendStatus</servlet-name>
|
||||
* <url-pattern>/phttpCheckSendStatus</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
*
|
||||
*/
|
||||
public class CheckSendStatusServlet extends PHTTPRelayServlet {
|
||||
/* URL parameters on the check */
|
||||
|
||||
/** H(routerIdent).toBase64() of the target to receive the message */
|
||||
public final static String PARAM_SEND_TARGET = "target";
|
||||
/** msgId parameter */
|
||||
public final static String PARAM_MSG_ID = "msgId";
|
||||
|
||||
public final static String PROP_STATUS = "status";
|
||||
public final static String STATUS_PENDING = "pending";
|
||||
public final static String STATUS_UNKNOWN = "unknown";
|
||||
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String target = req.getParameter(PARAM_SEND_TARGET);
|
||||
String msgIdStr = req.getParameter(PARAM_MSG_ID);
|
||||
|
||||
log("Checking status of [" + target + "] message [" + msgIdStr + "]");
|
||||
if (!isKnownMessage(target, msgIdStr)) {
|
||||
log("Not known - its not pending");
|
||||
notPending(req, resp);
|
||||
return;
|
||||
} else {
|
||||
log("Known - its still pending");
|
||||
pending(req, resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isKnownMessage(String target, String msgId) throws IOException {
|
||||
if ( (target == null) || (target.trim().length() <= 0) ) return false;
|
||||
if ( (msgId == null) || (msgId.trim().length() <= 0) ) return false;
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
File msgFile = new File(identDir, "msg" + msgId + ".dat");
|
||||
if (msgFile.exists())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void pending(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_PENDING).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
private void notPending(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_UNKNOWN).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
}
|
40
apps/phttprelay/java/src/net/i2p/phttprelay/LockManager.java
Normal file
40
apps/phttprelay/java/src/net/i2p/phttprelay/LockManager.java
Normal file
@ -0,0 +1,40 @@
|
||||
package net.i2p.phttprelay;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Lock identities for updating messages (so that they aren't read / deleted
|
||||
* while being written)
|
||||
*
|
||||
*/
|
||||
class LockManager {
|
||||
private volatile static Set _locks = new HashSet(); // target
|
||||
|
||||
public static void lockIdent(String target) {
|
||||
while (true) {
|
||||
synchronized (_locks) {
|
||||
if (!_locks.contains(target)) {
|
||||
_locks.add(target);
|
||||
return;
|
||||
}
|
||||
try { _locks.wait(1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void unlockIdent(String target) {
|
||||
synchronized (_locks) {
|
||||
_locks.remove(target);
|
||||
_locks.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package net.i2p.phttprelay;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
abstract class PHTTPRelayServlet extends HttpServlet {
|
||||
private Log _log = new Log(getClass());
|
||||
protected String _baseDir;
|
||||
|
||||
/* config params */
|
||||
/*public final static String PARAM_BASEDIR = "baseDir";*/
|
||||
public final static String ENV_BASEDIR = "phttpRelay.baseDir";
|
||||
|
||||
/** match the clock fudge factor on the router, rather than importing the entire router cvs module */
|
||||
public final static long CLOCK_FUDGE_FACTOR = 1*60*1000;
|
||||
|
||||
protected String buildURL(HttpServletRequest req, String path) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(req.getScheme()).append("://");
|
||||
buf.append(req.getServerName()).append(":").append(req.getServerPort());
|
||||
buf.append(req.getContextPath());
|
||||
buf.append(path);
|
||||
log("URL built: " + buf.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected File getIdentDir(String target) throws IOException {
|
||||
if ( (_baseDir == null) || (target == null) ) throw new IOException("dir not specified to deal with");
|
||||
File baseDir = new File(_baseDir);
|
||||
if (!baseDir.exists()) {
|
||||
boolean created = baseDir.mkdirs();
|
||||
log("Creating PHTTP Relay Base Directory: " + baseDir.getAbsolutePath() + " - ok? " + created);
|
||||
}
|
||||
File identDir = new File(baseDir, target);
|
||||
log("Ident dir: " + identDir.getAbsolutePath());
|
||||
return identDir;
|
||||
}
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
String dir = System.getProperty(ENV_BASEDIR);
|
||||
if (dir == null) {
|
||||
_log.warn("Base directory for the polling http relay system not in the environment [" + ENV_BASEDIR +"]");
|
||||
_log.warn("Setting the base directory to ./relayDir for " + getServletName());
|
||||
_baseDir = ".relayDir";
|
||||
} else {
|
||||
_baseDir = dir;
|
||||
log("Loaded up " + getServletName() + " with base directory " + _baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void log(String msg) {
|
||||
_log.debug(msg);
|
||||
}
|
||||
public void log(String msg, Throwable t) {
|
||||
_log.debug(msg, t);
|
||||
}
|
||||
}
|
263
apps/phttprelay/java/src/net/i2p/phttprelay/PollServlet.java
Normal file
263
apps/phttprelay/java/src/net/i2p/phttprelay/PollServlet.java
Normal file
@ -0,0 +1,263 @@
|
||||
package net.i2p.phttprelay;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Handle poll requests for new messages - checking the poll request for a valid signature,
|
||||
* sending back all of the messages found, and after all messages are written out, delete
|
||||
* them from the local store. If the signature fails, it sends back an HTTP 403 (UNAUTHORIZED).
|
||||
* If the target is not registered, it sends back an HTTP 404 (NOT FOUND) <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>Poll</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.PollServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>Poll</servlet-name>
|
||||
* <url-pattern>/phttpPoll</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
*
|
||||
*/
|
||||
public class PollServlet extends PHTTPRelayServlet {
|
||||
/* URL parameters on the check */
|
||||
|
||||
/** H(routerIdent).toBase64() of the target to receive the message */
|
||||
public final static String PARAM_SEND_TARGET = "target";
|
||||
|
||||
/** HTTP error code if the target is not known*/
|
||||
public final static int CODE_UNKNOWN = HttpServletResponse.SC_NOT_FOUND;
|
||||
/** HTTP error code if the signature failed */
|
||||
public final static int CODE_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
|
||||
/** HTTP error code if everything is ok */
|
||||
public final static int CODE_OK = HttpServletResponse.SC_OK;
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
byte data[] = getData(req);
|
||||
if (data == null) return;
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
String target = getTarget(bais);
|
||||
if (target == null) {
|
||||
log("Target not specified");
|
||||
resp.sendError(CODE_UNKNOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isKnown(target)) {
|
||||
resp.sendError(CODE_UNKNOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthorized(target, bais)) {
|
||||
resp.sendError(CODE_UNAUTHORIZED);
|
||||
return;
|
||||
} else {
|
||||
log("Authorized access for target " + target);
|
||||
}
|
||||
|
||||
sendMessages(resp, target);
|
||||
}
|
||||
|
||||
private byte[] getData(HttpServletRequest req) throws ServletException, IOException {
|
||||
ServletInputStream in = req.getInputStream();
|
||||
int len = req.getContentLength();
|
||||
byte data[] = new byte[len];
|
||||
int cur = 0;
|
||||
int read = DataHelper.read(in, data);
|
||||
if (read != len) {
|
||||
log("Size read is incorrect [" + read + " instead of expected " + len + "]");
|
||||
return null;
|
||||
} else {
|
||||
log("Read data length: " + data.length + " in base64: " + Base64.encode(data));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private String getTarget(InputStream in) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
int numBytes = 0;
|
||||
int c = 0;
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (c == (int)'&') break;
|
||||
buf.append((char)c);
|
||||
numBytes++;
|
||||
if (numBytes > 128) {
|
||||
log("Target didn't find the & after 128 bytes [" + buf.toString() + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (buf.toString().indexOf("target=") != 0) {
|
||||
log("Did not start with target= [" + buf.toString() + "]");
|
||||
return null;
|
||||
}
|
||||
return buf.substring("target=".length());
|
||||
}
|
||||
|
||||
private void sendMessages(HttpServletResponse resp, String target) throws IOException {
|
||||
log("Before lock " + target);
|
||||
LockManager.lockIdent(target);
|
||||
log("Locked " + target);
|
||||
try {
|
||||
File identDir = getIdentDir(target);
|
||||
expire(identDir);
|
||||
File messageFiles[] = identDir.listFiles();
|
||||
resp.setStatus(CODE_OK);
|
||||
log("Sending back " + (messageFiles.length -1) + " messages");
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
DataHelper.writeDate(out, new Date(Clock.getInstance().now()));
|
||||
DataHelper.writeLong(out, 2, messageFiles.length -1);
|
||||
for (int i = 0; i < messageFiles.length; i++) {
|
||||
if ("identity.dat".equals(messageFiles[i].getName())) {
|
||||
// skip
|
||||
} else {
|
||||
log("Message file " + messageFiles[i].getName() + " is " + messageFiles[i].length() + " bytes");
|
||||
DataHelper.writeLong(out, 4, messageFiles[i].length());
|
||||
writeFile(out, messageFiles[i]);
|
||||
boolean deleted = messageFiles[i].delete();
|
||||
if (!deleted) {
|
||||
log("!!!Error removing message file " + messageFiles[i].getAbsolutePath() + " - please delete!");
|
||||
}
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Error sending message", dfe);
|
||||
} finally {
|
||||
LockManager.unlockIdent(target);
|
||||
log("Unlocked " + target);
|
||||
}
|
||||
}
|
||||
|
||||
private final static long EXPIRE_DELAY = 60*1000; // expire messages every minute
|
||||
|
||||
private void expire(File identDir) throws IOException {
|
||||
File files[] = identDir.listFiles();
|
||||
long now = System.currentTimeMillis();
|
||||
for (int i = 0 ; i < files.length; i++) {
|
||||
if ("identity.dat".equals(files[i].getName())) {
|
||||
continue;
|
||||
}
|
||||
if (files[i].lastModified() + EXPIRE_DELAY < now) {
|
||||
log("Expiring " + files[i].getAbsolutePath());
|
||||
files[i].delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFile(ServletOutputStream out, File file) throws IOException {
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
try {
|
||||
byte buf[] = new byte[4096];
|
||||
while (true) {
|
||||
int read = DataHelper.read(fis, buf);
|
||||
if (read > 0)
|
||||
out.write(buf, 0, read);
|
||||
else
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isKnown(String target) throws IOException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAuthorized(String target, InputStream in) throws IOException {
|
||||
RouterIdentity ident = null;
|
||||
try {
|
||||
ident = getRouterIdentity(target);
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Identity was not valid", dfe);
|
||||
}
|
||||
|
||||
if (ident == null) {
|
||||
log("Identity not registered");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
long val = DataHelper.readLong(in, 4);
|
||||
Signature sig = new Signature();
|
||||
sig.readBytes(in);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataHelper.writeLong(baos, 4, val);
|
||||
if (DSAEngine.getInstance().verifySignature(sig, baos.toByteArray(), ident.getSigningPublicKey())) {
|
||||
return true;
|
||||
} else {
|
||||
log("Signature does NOT match");
|
||||
return false;
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Format error reading the nonce and signature", dfe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private RouterIdentity getRouterIdentity(String target) throws IOException, DataFormatException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
ident.readBytes(new FileInputStream(identFile));
|
||||
return ident;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
154
apps/phttprelay/java/src/net/i2p/phttprelay/RegisterServlet.java
Normal file
154
apps/phttprelay/java/src/net/i2p/phttprelay/RegisterServlet.java
Normal file
@ -0,0 +1,154 @@
|
||||
package net.i2p.phttprelay;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Accept registrations for PHTTP relaying, allowing the Polling HTTP (PHTTP)
|
||||
* transport for I2P to bridge past firewalls, NATs, and proxy servers. <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>Register</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.RegisterServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>pollPath</param-name>
|
||||
* <param-value>phttpPoll</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>sendPath</param-name>
|
||||
* <param-value>phttpSend</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>Register</servlet-name>
|
||||
* <url-pattern>/phttpRegister</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
* pollPath is the path under the current host that requests polling for messages should be sent
|
||||
* sendPath is the path under the current host that requests submitting messages should be sent
|
||||
*
|
||||
* The pollPath and sendPath must not start with / as they are translated ala http://host:port/[path]
|
||||
*/
|
||||
public class RegisterServlet extends PHTTPRelayServlet {
|
||||
private String _pollPath;
|
||||
private String _sendPath;
|
||||
|
||||
/* config params */
|
||||
public final static String PARAM_POLL_PATH = "pollPath";
|
||||
public final static String PARAM_SEND_PATH = "sendPath";
|
||||
|
||||
/* key=val keys sent back on registration */
|
||||
public final static String PROP_STATUS = "status";
|
||||
public final static String PROP_POLL_URL = "pollURL";
|
||||
public final static String PROP_SEND_URL = "sendURL";
|
||||
public final static String PROP_TIME_OFFSET = "timeOffset"; // ms (local-remote)
|
||||
|
||||
/* values for the PROP_STATUS */
|
||||
public final static String STATUS_FAILED = "failed";
|
||||
public final static String STATUS_REGISTERED = "registered";
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
ServletInputStream in = req.getInputStream();
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
try {
|
||||
Date remoteTime = DataHelper.readDate(in);
|
||||
long skew = getSkew(remoteTime);
|
||||
ident.readBytes(in);
|
||||
boolean ok = registerIdent(ident);
|
||||
sendURLs(req, resp, skew, ok);
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Invalid format for router identity posted", dfe);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
private long getSkew(Date remoteDate) {
|
||||
if (remoteDate == null) {
|
||||
log("*ERROR: remote date was null");
|
||||
return Long.MAX_VALUE;
|
||||
} else {
|
||||
long diff = Clock.getInstance().now() - remoteDate.getTime();
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean registerIdent(RouterIdentity ident) throws DataFormatException, IOException {
|
||||
File identDir = getIdentDir(ident.getHash().toBase64());
|
||||
boolean created = identDir.mkdirs();
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(identFile);
|
||||
ident.writeBytes(fos);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
log("Identity registered into " + identFile.getAbsolutePath());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendURLs(HttpServletRequest req, HttpServletResponse resp, long skew, boolean ok) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
|
||||
log("*Debug: clock skew of " + skew + "ms (local-remote)");
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
if (ok) {
|
||||
buf.append(PROP_POLL_URL).append("=").append(buildURL(req, _pollPath)).append("\n");
|
||||
buf.append(PROP_SEND_URL).append("=").append(buildURL(req, _sendPath)).append("\n");
|
||||
buf.append(PROP_TIME_OFFSET).append("=").append(skew).append("\n");
|
||||
buf.append(PROP_STATUS).append("=").append(STATUS_REGISTERED).append("\n");
|
||||
} else {
|
||||
buf.append(PROP_TIME_OFFSET).append("=").append(skew).append("\n");
|
||||
buf.append(PROP_STATUS).append("=").append(STATUS_FAILED).append("\n");
|
||||
}
|
||||
out.write(buf.toString().getBytes());
|
||||
out.close();
|
||||
}
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
|
||||
String pollPath = config.getInitParameter(PARAM_POLL_PATH);
|
||||
if (pollPath == null)
|
||||
throw new ServletException("Polling path for the registration servlet required [" + PARAM_POLL_PATH + "]");
|
||||
else
|
||||
_pollPath = pollPath;
|
||||
String sendPath = config.getInitParameter(PARAM_SEND_PATH);
|
||||
if (sendPath == null)
|
||||
throw new ServletException("Sending path for the registration servlet required [" + PARAM_SEND_PATH + "]");
|
||||
else
|
||||
_sendPath = sendPath;
|
||||
}
|
||||
}
|
318
apps/phttprelay/java/src/net/i2p/phttprelay/SendServlet.java
Normal file
318
apps/phttprelay/java/src/net/i2p/phttprelay/SendServlet.java
Normal file
@ -0,0 +1,318 @@
|
||||
package net.i2p.phttprelay;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Accept messages for PHTTP relaying, allowing the Polling HTTP (PHTTP)
|
||||
* transport for I2P to bridge past firewalls, NATs, and proxy servers. This
|
||||
* delivers them into the queue, returning HTTP 201 (created) if the queue is
|
||||
* known, as well as the URL at which requests can be made to check the delivery
|
||||
* status of the message. If the queue is not known, HTTP 410 (resource gone) is
|
||||
* sent back. <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>Send</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.SendServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>checkPath</param-name>
|
||||
* <param-value>phttpCheckStatus</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>maxMessagesPerIdent</param-name>
|
||||
* <param-value>100</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>Send</servlet-name>
|
||||
* <url-pattern>/phttpSend</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
* checkPath is the path under the current host that requests for the status of delivery should be sent
|
||||
* maxMessagesPerIdent is the maximum number of outstanding messages per peer being relayed
|
||||
*
|
||||
* The checkPath must not start with / as they are translated ala http://host:port/[path]
|
||||
*/
|
||||
public class SendServlet extends PHTTPRelayServlet {
|
||||
private String _checkPath;
|
||||
private int _maxMessagesPerIdent;
|
||||
|
||||
/* config params */
|
||||
public final static String PARAM_CHECK_PATH = "checkPath";
|
||||
public final static String PARAM_MAX_MESSAGES_PER_IDENT = "maxMessagesPerIdent";
|
||||
|
||||
/* URL parameters on the send */
|
||||
|
||||
/** H(routerIdent).toBase64() of the target to receive the message */
|
||||
public final static String PARAM_SEND_TARGET = "target";
|
||||
/** # ms to wait for the message to be delivered before failing it */
|
||||
public final static String PARAM_SEND_TIMEOUTMS = "timeoutMs";
|
||||
/** # bytes to be sent in the message */
|
||||
public final static String PARAM_SEND_DATA_LENGTH = "dataLength";
|
||||
/** sending router's time in ms */
|
||||
public final static String PARAM_SEND_TIME = "localTime";
|
||||
|
||||
/** msgId parameter to access the check path servlet with (along side PARAM_SEND_TARGET) */
|
||||
public final static String PARAM_MSG_ID = "msgId";
|
||||
|
||||
|
||||
/* key=val keys sent back on registration */
|
||||
public final static String PROP_CHECK_URL = "statusCheckURL";
|
||||
public final static String PROP_STATUS = "status";
|
||||
public final static String STATUS_OK = "accepted";
|
||||
public final static String STATUS_UNKNOWN = "unknown";
|
||||
private final static String STATUS_CLOCKSKEW = "clockSkew_"; /** prefix for (local-remote) */
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
ServletInputStream in = req.getInputStream();
|
||||
try {
|
||||
int contentLen = req.getContentLength();
|
||||
String firstLine = getFirstLine(in, contentLen);
|
||||
if (firstLine == null) {
|
||||
return;
|
||||
}
|
||||
Map params = getParameters(firstLine);
|
||||
String target = (String)params.get(PARAM_SEND_TARGET);
|
||||
String timeoutStr = (String)params.get(PARAM_SEND_TIMEOUTMS);
|
||||
String lenStr = (String)params.get(PARAM_SEND_DATA_LENGTH);
|
||||
String remoteTimeStr = (String)params.get(PARAM_SEND_TIME);
|
||||
long skew = 0;
|
||||
try {
|
||||
long remTime = Long.parseLong(remoteTimeStr);
|
||||
skew = System.currentTimeMillis() - remTime;
|
||||
} catch (Throwable t) {
|
||||
skew = Long.MAX_VALUE;
|
||||
log("*ERROR could not parse the remote time from [" + remoteTimeStr + "]");
|
||||
}
|
||||
|
||||
log("Target [" + target + "] timeout [" + timeoutStr + "] length [" + lenStr + "] skew [" + skew + "]");
|
||||
|
||||
if ( (skew > CLOCK_FUDGE_FACTOR) || (skew < 0 - CLOCK_FUDGE_FACTOR) ) {
|
||||
log("Attempt to send by a skewed router: skew = " + skew + "ms (local-remote)");
|
||||
failSkewed(req, resp, skew);
|
||||
}
|
||||
|
||||
if (!isValidTarget(target)) {
|
||||
log("Attempt to send to an invalid target [" + target + "]");
|
||||
fail(req, resp, "Unknown or invalid target");
|
||||
return;
|
||||
}
|
||||
|
||||
long len = -1;
|
||||
try {
|
||||
len = Long.parseLong(lenStr);
|
||||
} catch (Throwable t) {
|
||||
log("Unable to parse length parameter [" + PARAM_SEND_DATA_LENGTH + "] (" + lenStr + ")");
|
||||
fail(req, resp, "Invalid length parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
int msgId = saveFile(in, resp, target, len);
|
||||
if (msgId >= 0) {
|
||||
sendSuccess(req, resp, target, msgId);
|
||||
} else {
|
||||
fail(req, resp, "Unable to queue up the message for delivery");
|
||||
}
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getFirstLine(ServletInputStream in, int len) throws ServletException, IOException {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
int numBytes = 0;
|
||||
int c = 0;
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (c == (int)'\n') break;
|
||||
buf.append((char)c);
|
||||
numBytes++;
|
||||
if (numBytes > 512) {
|
||||
log("First line is > 512 bytes [" + buf.toString() + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
log("First line: " + buf.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static Map getParameters(String line) {
|
||||
//StringTokenizer tok = new StringTokenizer(line, "&=", true);
|
||||
Map params = new HashMap();
|
||||
while (line != null) {
|
||||
String key = null;
|
||||
String val = null;
|
||||
int firstAmp = line.indexOf('&');
|
||||
int firstEq = line.indexOf('=');
|
||||
if (firstAmp > 0) {
|
||||
key = line.substring(0, firstEq);
|
||||
val = line.substring(firstEq+1, firstAmp);
|
||||
line = line.substring(firstAmp+1);
|
||||
params.put(key, val);
|
||||
} else {
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private boolean isValidTarget(String target) throws IOException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
String files[] = identDir.list();
|
||||
// we skip 1 because of identity.dat
|
||||
if (files.length -1 > _maxMessagesPerIdent) {
|
||||
log("Too many messages pending for " + target + ": " + (files.length-1));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
log("Ident directory exists, but identity does not... corrupt for " + target);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log("Unknown ident " + target);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int saveFile(InputStream in, HttpServletResponse resp, String target, long len) throws IOException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (!identDir.exists()) return -1;
|
||||
try {
|
||||
LockManager.lockIdent(target);
|
||||
int i = 0;
|
||||
while (true) {
|
||||
File curFile = new File(identDir, "msg" + i + ".dat");
|
||||
if (!curFile.exists()) {
|
||||
boolean ok = writeFile(curFile, in, len);
|
||||
if (ok)
|
||||
return i;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
LockManager.unlockIdent(target);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeFile(File file, InputStream in, long len) throws IOException {
|
||||
long remaining = len;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
byte buf[] = new byte[4096];
|
||||
while (remaining > 0) {
|
||||
int read = in.read(buf);
|
||||
if (read == -1)
|
||||
break;
|
||||
remaining -= read;
|
||||
if (read > 0)
|
||||
fos.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (remaining != 0) {
|
||||
log("Invalid remaining bytes [" + remaining + " out of " + len + "] - perhaps message was cancelled partway through delivery? deleting " + file.getAbsolutePath());
|
||||
boolean deleted = file.delete();
|
||||
if (!deleted)
|
||||
log("!!!Error deleting temporary file " + file.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendSuccess(HttpServletRequest req, HttpServletResponse resp, String target, int msgId) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_OK).append('\n');
|
||||
buf.append(PROP_CHECK_URL).append('=').append(buildURL(req, _checkPath));
|
||||
buf.append('?');
|
||||
buf.append(PARAM_SEND_TARGET).append('=').append(target).append("&");
|
||||
buf.append(PARAM_MSG_ID).append('=').append(msgId).append("\n");
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void fail(HttpServletRequest req, HttpServletResponse resp, String err) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_UNKNOWN).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void failSkewed(HttpServletRequest req, HttpServletResponse resp, long skew) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_CLOCKSKEW).append(skew).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
|
||||
String checkPath = config.getInitParameter(PARAM_CHECK_PATH);
|
||||
if (checkPath == null)
|
||||
throw new ServletException("Check status path for the sending servlet required [" + PARAM_CHECK_PATH + "]");
|
||||
else
|
||||
_checkPath = checkPath;
|
||||
|
||||
String maxMessagesPerIdentStr = config.getInitParameter(PARAM_MAX_MESSAGES_PER_IDENT);
|
||||
if (maxMessagesPerIdentStr == null)
|
||||
throw new ServletException("Max messages per ident for the sending servlet required [" + PARAM_MAX_MESSAGES_PER_IDENT + "]");
|
||||
try {
|
||||
_maxMessagesPerIdent = Integer.parseInt(maxMessagesPerIdentStr);
|
||||
} catch (Throwable t) {
|
||||
throw new ServletException("Valid max messages per ident for the sending servlet required [" + PARAM_MAX_MESSAGES_PER_IDENT + "]");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
String line = "target=pp0ARjQiB~IKC-0FsMUsPEMrwR3gxVBZGRYfEr1IzHI=&timeoutMs=52068&dataLength=2691&";
|
||||
Map props = getParameters(line);
|
||||
for (java.util.Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = (String)props.get(key);
|
||||
System.out.println("[" + key + "] = [" + val + "]");
|
||||
}
|
||||
}
|
||||
}
|
71
apps/phttprelay/java/web.xml
Normal file
71
apps/phttprelay/java/web.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
|
||||
"http://java.sun.com/dtd/web-app_2_3.dtd">
|
||||
|
||||
|
||||
<web-app>
|
||||
<display-name>I2P Polling HTTP Relay</display-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Register</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.RegisterServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>pollPath</param-name>
|
||||
<param-value>/phttpPoll</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>sendPath</param-name>
|
||||
<param-value>/phttpSend</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Send</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.SendServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>checkPath</param-name>
|
||||
<param-value>/phttpCheckSendStatus</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>maxMessagesPerIdent</param-name>
|
||||
<param-value>100</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>CheckSendStatus</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.CheckSendStatusServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Poll</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.PollServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Register</servlet-name>
|
||||
<url-pattern>/phttpRegister</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Send</servlet-name>
|
||||
<url-pattern>/phttpSend</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>CheckSendStatus</servlet-name>
|
||||
<url-pattern>/phttpCheckSendStatus</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Poll</servlet-name>
|
||||
<url-pattern>/phttpPoll</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
278
apps/tests/COPYING
Normal file
278
apps/tests/COPYING
Normal file
@ -0,0 +1,278 @@
|
||||
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.
|
44
apps/tests/EchoServer.java
Normal file
44
apps/tests/EchoServer.java
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* A Minimal echo server.
|
||||
*
|
||||
* Copyright (c) 2004 Michael Schierl
|
||||
*
|
||||
* Licensed unter GNU General Public License.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
public class EchoServer extends Thread {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
ServerSocket ss = new ServerSocket(Integer.parseInt(args[0]));
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
new EchoServer(s);
|
||||
}
|
||||
}
|
||||
|
||||
private Socket s;
|
||||
|
||||
public EchoServer(Socket s) {
|
||||
this.s=s;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = s.getOutputStream();
|
||||
byte[] b = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(b)) != -1) {
|
||||
out.write(b, 0, len);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
// nothing
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
106
apps/tests/GuaranteedBug.java
Normal file
106
apps/tests/GuaranteedBug.java
Normal file
@ -0,0 +1,106 @@
|
||||
// compile & run this file against i2p.jar
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
public class GuaranteedBug {
|
||||
|
||||
|
||||
public void reproduce() {
|
||||
try {
|
||||
Destination d1 = null;
|
||||
// first client (receiver)
|
||||
if (true) { // smaller scope for variables ...
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream keyStream =
|
||||
new ByteArrayOutputStream(512);
|
||||
d1 = client.createDestination(keyStream);
|
||||
ByteArrayInputStream in =
|
||||
new ByteArrayInputStream(keyStream.toByteArray());
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY,
|
||||
I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
I2PSession session = client.createSession(in, opts);
|
||||
session.connect();
|
||||
session.setSessionListener(new PacketCounter());
|
||||
}
|
||||
// second client (sender)
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream keyStream = new ByteArrayOutputStream(512);
|
||||
Destination d2 = client.createDestination(keyStream);
|
||||
ByteArrayInputStream in =
|
||||
new ByteArrayInputStream(keyStream.toByteArray());
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY,
|
||||
I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
I2PSession session = client.createSession(in, opts);
|
||||
session.connect();
|
||||
session.setSessionListener(new DummyListener());
|
||||
for (int i=0;i<1000; i++) {
|
||||
byte[] msg = (""+i).getBytes("ISO-8859-1");
|
||||
session.sendMessage(d1,msg);
|
||||
System.out.println(">>"+i);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (I2PException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new GuaranteedBug().reproduce();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
public class DummyListener implements I2PSessionListener {
|
||||
public void disconnected(I2PSession session) {
|
||||
System.err.println("Disconnected: "+session);
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message,
|
||||
Throwable error) {
|
||||
System.err.println("Error: "+session+"/"+message);
|
||||
error.printStackTrace();
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId,
|
||||
long size) {
|
||||
System.err.println("Message here? "+session);
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
System.err.println("Abuse: "+severity+"/"+session);
|
||||
}
|
||||
}
|
||||
|
||||
public class PacketCounter extends DummyListener {
|
||||
private int lastPacket = -1;
|
||||
public void messageAvailable(I2PSession session, int msgId,
|
||||
long size) {
|
||||
try {
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
String m = new String(msg, "ISO-8859-1");
|
||||
int no = Integer.parseInt(m);
|
||||
if (no != ++lastPacket) {
|
||||
System.out.println("ERROR: <<"+no);
|
||||
} else {
|
||||
System.out.println("<<"+no);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (I2PException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
apps/tests/README
Normal file
6
apps/tests/README
Normal file
@ -0,0 +1,6 @@
|
||||
This directory is intended for tests which are useful for testing
|
||||
I2P, but don't test any of the I2P components directly. Instead,
|
||||
tests are run on "application" level (TCP, IRC, HTTP etc.).
|
||||
|
||||
IOW: These tests may be useful for any other project that allows
|
||||
tunneling of "normal" protocols, not only I2P.
|
94
apps/tests/echotester/BasicEchoTestAnalyzer.java
Normal file
94
apps/tests/echotester/BasicEchoTestAnalyzer.java
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* A basic implementation for the EchoTestAnalyzer.
|
||||
*/
|
||||
public class BasicEchoTestAnalyzer implements EchoTestAnalyzer {
|
||||
|
||||
/**
|
||||
* How many events must appear until a detailed report is
|
||||
* printed. Default is every 20 events.
|
||||
*/
|
||||
private static int REPORT_DELAY = 20;
|
||||
|
||||
private static int SUMMARY_SIZE = 100;
|
||||
|
||||
public BasicEchoTestAnalyzer() {
|
||||
this(20, 100);
|
||||
}
|
||||
|
||||
public BasicEchoTestAnalyzer(int reportDelay, int summarySize) {
|
||||
REPORT_DELAY = reportDelay;
|
||||
SUMMARY_SIZE = summarySize;
|
||||
}
|
||||
|
||||
private int events = 0,
|
||||
packetLosses = 0,
|
||||
packetLossesDisconnect=0,
|
||||
disconnects = 0,
|
||||
disconnectsRefused = 0,
|
||||
delayCount=0,
|
||||
lastDelayPtr = 0;
|
||||
private long minDelay=Long.MAX_VALUE, maxDelay = 0, delaySum=0;
|
||||
private long[] lastDelays = new long[SUMMARY_SIZE];
|
||||
|
||||
|
||||
public synchronized void packetLossOccurred(boolean beforeDisconnect) {
|
||||
System.out.println("1: Packet lost"+
|
||||
(beforeDisconnect?" before disconnect":"")+
|
||||
".");
|
||||
packetLosses++;
|
||||
if (beforeDisconnect) packetLossesDisconnect++;
|
||||
countEvent();
|
||||
}
|
||||
|
||||
public synchronized void successOccurred(long delay) {
|
||||
System.out.println("0: Delay = "+delay);
|
||||
if (delay > maxDelay) maxDelay=delay;
|
||||
if (delay < minDelay) minDelay=delay;
|
||||
delaySum+=delay;
|
||||
delayCount++;
|
||||
lastDelays[lastDelayPtr++]=delay;
|
||||
lastDelayPtr%=SUMMARY_SIZE;
|
||||
countEvent();
|
||||
}
|
||||
|
||||
public synchronized void disconnected(boolean refused) {
|
||||
System.out.println("2: Disconnected"+
|
||||
(refused?" (connection refused)":"")+
|
||||
".");
|
||||
disconnects++;
|
||||
if (refused) disconnectsRefused++;
|
||||
countEvent();
|
||||
}
|
||||
|
||||
private void countEvent() {
|
||||
events++;
|
||||
if (events % REPORT_DELAY == 0) {
|
||||
int packets = packetLosses+delayCount;
|
||||
long delaySummary=0;
|
||||
for (int i=0;i<SUMMARY_SIZE;i++) {
|
||||
delaySummary+=lastDelays[i];
|
||||
}
|
||||
System.out.println
|
||||
("++++++++++++++++ ECHO STATISTICS +++++++++++++++++++++++++"+
|
||||
"\n++ Number of total echo messages: "+packets+
|
||||
"\n++ No response for "+packetLosses+
|
||||
"\n++ (of which "+ packetLossesDisconnect+
|
||||
" due to a disconnect)"+
|
||||
"\n++ Disconnects: "+disconnects+
|
||||
"\n++ (of which "+disconnectsRefused+
|
||||
" due to 'connection refused')"+
|
||||
(disconnects>0 || true
|
||||
?"\n++ Average lost packets per disconnect: "+
|
||||
(packetLossesDisconnect/(float)disconnects)
|
||||
:"")+
|
||||
"\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++"+
|
||||
"\n++ Minimal delay: "+minDelay+
|
||||
"\n++ Average delay: "+(delaySum/(float)delayCount)+
|
||||
"\n++ Maximal delay: "+maxDelay+
|
||||
(delayCount >=SUMMARY_SIZE
|
||||
?"\n++ Average delay over last " + SUMMARY_SIZE + ": "+(delaySummary/(float)SUMMARY_SIZE)
|
||||
:"")+
|
||||
"\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
}
|
||||
}
|
||||
}
|
19
apps/tests/echotester/EchoTestAnalyzer.java
Normal file
19
apps/tests/echotester/EchoTestAnalyzer.java
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
/**
|
||||
* A class that wants to analyze tests implements this interface. This
|
||||
* allows to "mix" several test values (from different echo servers)
|
||||
* as well as different algorithms for analyzing the data (for
|
||||
* jrandom: Strategy Pattern *g*).
|
||||
*/
|
||||
public interface EchoTestAnalyzer {
|
||||
|
||||
public void packetLossOccurred(boolean beforeDisconnect);
|
||||
|
||||
public void successOccurred(long delay);
|
||||
|
||||
public void disconnected(boolean refused);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
170
apps/tests/echotester/EchoTester.java
Normal file
170
apps/tests/echotester/EchoTester.java
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Test for an echo server. This test is intended to be used via an
|
||||
* I2PTunnel, but should work as well on other networks that provide
|
||||
* TCP tunneling and an echo server.
|
||||
*
|
||||
* Copyright (c) 2004 Michael Schierl
|
||||
*
|
||||
* Licensed unter GNU General Public License.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* The main engine for the EchoTester.
|
||||
*/
|
||||
public class EchoTester extends Thread {
|
||||
|
||||
/**
|
||||
* How long to wait between packets. Default is 6 seconds.
|
||||
*/
|
||||
private static long PACKET_DELAY= 6000;
|
||||
|
||||
/**
|
||||
* How many packets may be on the way before the connection is
|
||||
* seen as "broken" and disconnected.
|
||||
*/
|
||||
private static final long MAX_PACKETS_QUEUED=50; // unused
|
||||
|
||||
|
||||
private EchoTestAnalyzer eta;
|
||||
private String host;
|
||||
private int port;
|
||||
|
||||
// the following vars are synchronized via the lock.
|
||||
private Object lock = new Object();
|
||||
private long nextPacket=0;
|
||||
private long nextUnreceived=0;
|
||||
private boolean readerRunning=false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 3)
|
||||
PACKET_DELAY = Long.parseLong(args[2]);
|
||||
new EchoTester(args[0], Integer.parseInt(args[1]),
|
||||
new BasicEchoTestAnalyzer());
|
||||
}
|
||||
|
||||
public EchoTester(String host, int port, EchoTestAnalyzer eta) {
|
||||
this.eta=eta;
|
||||
this.host=host;
|
||||
this.port=port;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
Socket s;
|
||||
try {
|
||||
s = new Socket(host, port);
|
||||
} catch (ConnectException ex) {
|
||||
eta.disconnected(true);
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
continue;
|
||||
}
|
||||
System.out.println("41: Connected to "+host+":"+port);
|
||||
synchronized(lock) {
|
||||
nextUnreceived=nextPacket;
|
||||
}
|
||||
Thread t = new ResponseReaderThread(s);
|
||||
Writer w = new BufferedWriter(new OutputStreamWriter
|
||||
(s.getOutputStream()));
|
||||
while (true) {
|
||||
long no;
|
||||
synchronized(lock) {
|
||||
no = nextPacket++;
|
||||
}
|
||||
try {
|
||||
w.write(no+" "+System.currentTimeMillis()+"\n");
|
||||
w.flush();
|
||||
} catch (SocketException ex) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
}
|
||||
s.close();
|
||||
t.join();
|
||||
synchronized(lock) {
|
||||
if (readerRunning) {
|
||||
System.out.println("*** WHY IS THIS THREAD STILL"+
|
||||
" RUNNING?");
|
||||
}
|
||||
while (nextUnreceived < nextPacket) {
|
||||
nextUnreceived++;
|
||||
eta.packetLossOccurred(true);
|
||||
}
|
||||
if (nextUnreceived > nextPacket) {
|
||||
System.out.println("*** WTF? "+nextUnreceived+" > "+
|
||||
nextPacket);
|
||||
}
|
||||
}
|
||||
eta.disconnected(false);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1); // treat these errors as fatal
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1); // treat these errors as fatal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ResponseReaderThread extends Thread {
|
||||
|
||||
private Socket s;
|
||||
|
||||
public ResponseReaderThread(Socket s) {
|
||||
this.s=s;
|
||||
synchronized(lock) {
|
||||
readerRunning=true;
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader
|
||||
(s.getInputStream()));
|
||||
String line;
|
||||
int index;
|
||||
while ((line=br.readLine()) != null) {
|
||||
if ((index=line.indexOf(" ")) == -1)
|
||||
continue;
|
||||
long now, packetNumber, packetTime;
|
||||
now = System.currentTimeMillis();
|
||||
try {
|
||||
packetNumber = Long.parseLong
|
||||
(line.substring(0,index));
|
||||
packetTime = Long.parseLong
|
||||
(line.substring(index+1));
|
||||
} catch (NumberFormatException ex) {
|
||||
System.out.println(ex.toString());
|
||||
continue;
|
||||
}
|
||||
synchronized (lock) {
|
||||
while (packetNumber > nextUnreceived) {
|
||||
nextUnreceived++;
|
||||
eta.packetLossOccurred(false);
|
||||
}
|
||||
if (nextUnreceived > packetNumber) {
|
||||
System.out.println("*** DOUBLE PACKET!");
|
||||
} else {
|
||||
nextUnreceived++;
|
||||
}
|
||||
}
|
||||
eta.successOccurred(now-packetTime);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
// ignore
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(0);
|
||||
}
|
||||
synchronized(lock) {
|
||||
readerRunning=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
apps/tests/readme.license.txt
Normal file
10
apps/tests/readme.license.txt
Normal file
@ -0,0 +1,10 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/tests module is the root of application
|
||||
level tests, and everything within it is released
|
||||
according to the terms of the I2P license policy.
|
||||
That means everything contained within the
|
||||
i2p/apps/tests module is released into the public
|
||||
domain unless otherwise marked. Alternate licenses
|
||||
that may be used include GPL, GPL + java exception,
|
||||
BSD, Cryptix, and MIT.
|
57
build.xml
Normal file
57
build.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2p">
|
||||
<target name="all" >
|
||||
<echo message="Useful targets: " />
|
||||
<echo message=" build: build existing code into ./build/, but dont clean" />
|
||||
<echo message=" javadoc: generate javadoc for the entire project into ./build/javadoc" />
|
||||
<echo message=" clean: clean up the ./build/ dir" />
|
||||
<echo message=" distclean: clean up all derived files" />
|
||||
<echo message=" dist: distclean, then build and javadoc" />
|
||||
</target>
|
||||
<target name="dist" depends="distclean, build, javadoc" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="core/java/" target="build" />
|
||||
<ant dir="router/java/" target="build" />
|
||||
<ant dir="apps/ministreaming/java/" target="build" />
|
||||
<ant dir="apps/i2ptunnel/java/" target="build" />
|
||||
<ant dir="apps/httptunnel/java/" target="build" />
|
||||
<ant dir="apps/phttprelay/java/" target="build" />
|
||||
<ant dir="installer/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile" />
|
||||
<target name="jar" depends="compile" >
|
||||
<copy file="core/java/build/i2p.jar" todir="build/" />
|
||||
<copy file="router/java/build/router.jar" todir="build/" />
|
||||
<copy file="apps/ministreaming/java/build/mstreaming.jar" todir="build/" />
|
||||
<copy file="apps/i2ptunnel/java/build/i2ptunnel.jar" todir="build/" />
|
||||
<copy file="apps/httptunnel/java/build/httptunnel.jar" todir="build/" />
|
||||
<copy file="apps/phttprelay/java/build/phttprelay.war" todir="build/" />
|
||||
<copy file="installer/java/build/install.jar" todir="build/" />
|
||||
<copy file="installer/java/build/guiinstall.jar" todir="build/" />
|
||||
<copy file="installer/java/build/fetchseeds.jar" todir="build/" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="core/java/src:core/java/test:router/java/src:router/java/test:apps/ministreaming/java/src:apps/i2ptunnel/java/src:apps/httptunnel/java/src"
|
||||
destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2P" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="core/java/" target="distclean" />
|
||||
<ant dir="router/java/" target="distclean" />
|
||||
<ant dir="apps/ministreaming/java/" target="distclean" />
|
||||
<ant dir="apps/i2ptunnel/java/" target="distclean" />
|
||||
<ant dir="apps/httptunnel/java/" target="distclean" />
|
||||
<ant dir="apps/phttprelay/java/" target="distclean" />
|
||||
<ant dir="installer/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
82
hosts.txt
Normal file
82
hosts.txt
Normal file
@ -0,0 +1,82 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.28 2004/03/19 21:58:44 jrandom Exp $
|
||||
; changelog:
|
||||
; (1.27) added jar.i2p
|
||||
; (1.26) added tor-www-proxy.i2p (WWW through tor by human - won't be up 24/7!)
|
||||
; (1.25) added ugha.i2p
|
||||
; (1.24) added reefer.i2p
|
||||
; (1.23) added nic.i2p
|
||||
; (1.22) added www.janonymous.i2p
|
||||
; (1.21) added bluebeam.i2p
|
||||
; (1.20) added echo.baffled.i2p (which runs mihi's echo server - telnet and get replies)
|
||||
; (1.19) added irc.baffled.i2p (which is hooked up with irc.duck.i2p over i2p)
|
||||
; (1.18) added www.aum.i2p, nntp.baffled.i2p, www.baffled.i2p, and ardvark.i2p
|
||||
; (1.17) added gernika's eepsite
|
||||
; (1.16) added duck's scp and hosting server
|
||||
; (1.15) added eco's beta i2psnark server
|
||||
; (1.14) added duck's pgp keyserver
|
||||
; (1.13) added human's eepsite, the mail servers, and luckypunk's eepsite
|
||||
; (1.12) added fillament's chessd server (FICS based clients or telnet to it)
|
||||
; (1.11) added mp3.tc.i2p and mesh.firerabbit.i2p
|
||||
; (1.10) added aum's mp3 and ogg streams
|
||||
; (1.9) added the FCP and HTTP keys for a private entropy node, as well as nm's eepsite
|
||||
; (1.8) updated nightblade's eepsite (after confirming ident w/ trent on iip)
|
||||
; (1.7) added aum's eepsite
|
||||
; (1.6) added fillament's flog/plog and eco's eepsite
|
||||
; (1.5) added duck's irc
|
||||
; (1.4) added eco's JAP tunnel
|
||||
; (1.3) added duck's eepsite
|
||||
; (1.2) fixed duck's subdomain names
|
||||
; (1.1) added nntp, jabber, squid, and i2pcvs (and imported into i2p's cvs for distribution)
|
||||
; (1.0) added dyad.i2p and nightblade.i2p
|
||||
; (0.5) added bozo.i2p
|
||||
; (0.0) added tc.i2p
|
||||
;
|
||||
tc.i2p=3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJcS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7gfqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teMd5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA
|
||||
dyad.i2p=W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYppK9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HCVilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA
|
||||
nightblade.i2p=nyErwSseXbsojcWtNkDyUul0YULtqr6qyWSzIp639Ygpe8juCdgPMLURVXcmlCvo~QPoHg6zt53KpgpGvB1-Wv2SGvc2Mvs~o8USw3ius8fP1URphqcBbulK8Ci0bgknt0kD0AfxqfMz-p~xk1QEMxq2kZEoB3oyIIFnQlpb2ByS74Lx8iKzXTrwWk19I3Dvu4nIq8CBDDwu3lYoCD2kC-jT5pjgglverGPEGN4o55LYVTtfSg4gAJFZeaE4KjBR5P1z7cca6UDjGMWfR0iCa8P3qpkY2ODYpk~8w2xgBbgDq-8Hzik~uraHc598ccS8QpwB0f0Jw~2PZcTjOPdZ-239U6p3tESXa7FXzRBCujv4Bx6CVFRhCmBHpyFnCD-MugZ~vR6XFSS2XBsCT~duXKq94HH2n1iAWslG4Vu44ut1JVhDPFzp~Dk7wujB0tCo2HXH2icRQxOWe37foU4LZSJ4oMpFDACBzwSfcZdIPsVRxGttKQx4yzgffR1Q~Jl7AAAA
|
||||
bozo.i2p=ubMPUwY0op6B7Jr8SAjY2bQXze8m1sT6xF2N0cv43dIHwLTO0gUqn7FCP9jXZDodE9DR3fu8fG8x1Yz1SpXFk4WtFmuDuhdN7uaHuLIQ71PATC2GRhDS7NXqn7GsVZgQxhHKenaE5BKjIKt2amZ2~8CM0qBKTqwievUO-Y6zG~-8l~RpnAxDZUMOjKKy5R3~jEN9DFZCaKvXSNcOVFjZRGaD6d8NvkAJjndHdE3bFSJUDNv0qhhp09-mm~Se9C~FzjrAbhgappdNRiwQepXTWqRbjjt6lUPT2eJISPDxYxoeZkBGZa9XmfO9QH3hoMo0g~RbwLeBqtgeRGhVgFiC4pN8lFt3z7j8L-12575SUeOnJPIm3hQWXdTjKX1hqf4LopYBG84N95IeydPJegsmkIkAMzEb0d~-UZfVSP9yFgs37j~Fds5yxBsu-NFc6qmZihpXEd7jrfX1-HuJVmXFmwaZgyumRSRDbj714wxr7RP4Hb-liA3JrU-FbqNQFoTMAAAA
|
||||
duck.i2p=eFJdRYFmtjcpx9-Mw3JBdF6fwtb9cHRBR103Q8IGc91Jdfn0iYzK6Xx0oIzPvpbD4yOlPQm-C-7eTahrAkHa2FMCRTiiVq2a0nBp37W1uTvAToV-MKYPKTdFMxrXxvjS7qaSUXdJRcPaPexolfx-Gcjh~rN2tKCh0mz9beueiQ18~8qWGh6hUMb0yyA09ipL9vIkmHmooLwT9AZyzHXEzdLXZe1P~CG3L46QaXp9aTD7EkAwG6VBMjQrGiSJ-9FFhx4QcYAZWM-dfrtzbbVYfHxqQRBwzB27zLlaKVaqu4enC0N1cW1yy-cjnv0Wxokqe62B2uPzFYtloxQpBPfTLQZUfUzjskY-3Yg2AdSbEu37jYsnAJA95AlLz4t1W1vPTNiXzCaRqkkMX342SUkJK-HZE3wTjAgGPqJ9vMaC2YexPFViTxEO2Q0jDSjdPHG0D769qehkN5Bfb8MEI-yr3g9zLaY~w4r6T38ap33qfyISWlIJ5qCPcRVkeK8OqZ4vAAAA
|
||||
irc.duck.i2p=Bxqr5E7-56oJeya1lsDLBN6L1gKme-FUS6Bh~TQS3HswomK9rpjrYNeqBTBoE8TCFl161~FI3soWqbnmFhIdhskausZsO0ez5-4IXMJW8NTilWqXQ3OJxA20M9grohx3RjkZgXU1ooTx7wviSHtXQYiiqnzGnIzmmEZo5-Xx6VjXakctebWwbi2PrsE6XLxrxXBzB34l4KlVsyX504BJiOT6KXNaVZxvG61GfGVfNHdeXljMDE5d25UdFC6RJdDnJ3Z7Yb7EjAww78aowbR0VCfJDH~cB868-VOKIxmor3Rs7giaLXmUyW~GRtFX10COJj5V6BrhKs61XOXxfbyQKGVXZ0mM2A8cdZE1ftr96SZgGy~V8uUHKvoa1HpjMNrPL5Tr6EGfJxOxAy6PHwotn6a8UMnCZgEdTbQ6U3BTywU~x3SCAQvfOT~dl3sZ-5ujYWNFRp7RhdY-WHn1Kj59MfU-VpczGYdV3bRkwT5lpIjST~vopLfkYUeB6gcVSr49AAAA
|
||||
jabber.duck.i2p=AzR8Zrms1sLXflvaZZ5CI2TS1cRvlO99D5Jh641-KS0lMSQPUnSApTfy9R4aPxZ3SWwpZPfSSGN-e7~kw8ucGQVB9EOHnht7WCz5UKC423vX4fFZqpTyqemDBx0CYXfhqRf-~7mjslXigR2nbQwh8NMl2bgLPknGe55wMlZPfsacS2WKNQYbdOvrgG6Zb6DK8RzTDrsum5h2jtIorv3CuMrNKLdfziXBDICep2Zp9jKDPBHgSsRgl6dW8A-5~WbkIPLpQwqeTzw90r8uJ9EIln~GlFulblJprCfLzxJ2LAFpxNcTkqzOJPElFfhjVrJOYbm6IfllBlHMVNbJjYTOsiJLqODxMiVt3pQx~AGFrx5mVtMUcz9cu9hVPnz2ZVmIDB~a4UGkexf~FCHhaQ6Emnr4nnZBiFSP3f4nASOMHLOs4SE3UKiYUB3ntmJssVdD1w-ZGrovHynkvXMjrJ9GhIeldFm0M5cOAhra0OtJagZ-bgphR0v99ADGPl7X5DoGAAAA
|
||||
home.duck.i2p=dQmTRCAgcKlbt3VKdW-iFI4zd3WnDaM8ULsfIZb8oEdDuTq5Sl~1JguWH2Wl8PMRuxkhHpfOZ6IUJZtxo-rr3tfaDsYPl8SZSIbvGBs5QooLl2NNTPA~gQMJOHZkuM4KWMWYnFRWoaqAUt9KAmdyl5vgF20xv0rmSgRmm8t6c3QSTfNWSeSDYkRgEumaUKP-kRJnSKNeIOez1N2HQSlaDu~cH27E-VqEuAu8HppI87dV~v6nVKXR9-KgHzCdfHXxy2pxV9trJ8vmTEZO53ab11-BdDMoZjOv5sjMUADNDnhIW1RJ3c-ZcF9OotYsUrf7E1fRo3BwGg27LKhFnyBDmzI0a5uQJ9HCDlzSQ7yfHNMqNCtMfHgZE6Bw6F3tJfr5ISP1j5ibfj4k9tODyfWVqTionQfgrP5szHdyGu14Y4AtDW01-d9BNmBTHA53jCl~Jq6Qm5~CKcGZGS-05iyhmg9x1FtR-rW5Vf6o8uitSjtE5uPt~UHXacaGxQMJSy9vAAAA
|
||||
nntp.duck.i2p=5SIb-1I6SVwFlW~XlYTNJU1NCpbyHJ3co8KdrpDxBosnHtdsZzq2qzYzGDfvkKK3WRoKmRGYCE77uXAvOQjEyWoTkkGeY2xl3B1t4t8K4j8dFvYtDJkRGePxGaY3MW-9ANGUQsGOhh6qq4lcQ7rv-AWyDftfZ3pGaHc79VukSo7-6OSh4WDw~0l~DjdFjQsZOVNTbKIfDxzSjKnkTpNc73nrLhRE5nmnMj7bljTzNtAHiVf~hFMndPxF80JZt6erLqy62-~XbevTWpn2KCTjYzhYUkwYW95-SW27ph5rIVZneizLEanSPtdUkDGhMEjjmy39Qr5rD5i9H-ioIP3NppRkjPwrtI9VJpsJtzv75uANIXy48RCBVXXA18ng8o7FVevdyjVb~C90IXpJF~mT7DI94rTT5QnzlpJVCid65kOoNzXFC80lP-iiwXMMBEcys8RGA9hdOvagkFDJ64l0GwrVpFGO2AVMsMenR0WHEvJOjNv62sT6rlntO3ZywhNXAAAA
|
||||
pgp.duck.i2p=kRm2KRa2EiWO~XQFYxSg6UM9lXYHZ93IB80j3ShFhJOOZ4AN05BrTGjMeT9nhn5n1LMEUhy9HJuN-Lhkd89Mbhufk7di6M40wqySns2g0T7XUCFUgQ8kl4~BoY8M9U0pHpM3RJcCw8WJEFGEk~fX8tgC4XQB43UIXfrdKTlNfKhxZODE4UdvlFfFdOYMgH53x8UgMZUprn~URTRNg1uwcaXr2luMwts6HnDt1bDd8elitsWViOJiw42yAFMqBnf-7mhiTCsoYg-nsOiq0Jirt58cBjAGUL5ujZo~vfXkLslDKjHOP32Y7HIi5ANEsbbRr~8QrVUojnVoJwFXs8BQBTevRpGLkCSLnKa31jNSt3msOnEP-n729Jabwj8o3pdRk9e-3~~y9gfj4bcmpSH9sOJoqmipXDiqOvv0tL9twERqse3tAwjE1NgXTvl5e2Zc~F0xJ-L2aG~6BX175ihjjEiYGWRYaoEisHMZdMtivsAK92dKl1JVEkuDF3W8KjL8AAAA
|
||||
scp.duck.i2p=AE2Ff7x-tJMI901UKEEXkcwb9~5KZKs--VBXEoZnnpC~mlgnqGsZr8MBvRvs6xAzP7xXLeL~cHQ~gvPFT60obEBitwH~JcKMahhJDGb0p23Z8B81QajXijypDpVfMDfFbMiqQGctXhnBidNKWe7HQEcJGZer-SCu3m8CiftcZ0t0g5Y67B2AtqLhza5xfepq6FQ-Tl6fpgS-UDcGUAqMpLUYfrBFB11oM09TRVsfNp~NIAvMdrvXiX8dTDUHJM4FRdpV2OsJiyDdgf5-W6s6ssxohviT5MdijUSYQw5sWj8~9Xkv~~aa11Dvty0E7K9IhXAeJlwBe3VuDizsAJGnFIU7PwZXV7-9-28Zgldarg1P-rh2QDvCqF1vjdyZe99BBcTiCqvE3zx2N-9eT~FeOURrgE~2rFBKVBZfuAASSORqiXIeAANKklCW2pGQrM3DkX8ybi93Weg~eBjwGQugO1FRZ-ISq7npRWruMiC7f~fWLqkmRUy9HahPNp8E41s9AAAA
|
||||
squid.i2p=8fiWZbRjOEzrj5n4jSqjN9UN54wTrsgEjqn7GRUQpLx1svf8lwckXPV5buP2VEYGo~83ftkIcDKyMLXkxSr8jqbb4yAEgPe2~w7OT~8LNvmVPz0xZhIO6fiw0WU4xD9x5PG1spYjWPnLFv7pynEvBpWFXaUlCacjWL2KkfViiGPXvveQqQIZs7VkxVD2HK-oT4yIjdqHpc7Y8nEV9xwds3-LX6to5p70jFe~kZJA2fjWHsSCm92TtPvoR3aTlL1VS3JUKpcH6BL5irsh-SKODEtDRCErPQI~j2SHzhcD6dMUsI7bm3AxivjFpSQHqyXLmLVdxECYsMET~nIHmuv8NYTHQQ0jM0XTQzwnQwEHjHRBd1~spR9uS2~LSnX4Pw~X1WTknJpPK1f4Cu1O44X4RYcLRCsxpEzytUBXA4BQizrbYgOJVGQa9-PNGxJeZsnNUZ3PxUi23Oh-c4jUaB0ZjyKTWJSpzj1GI6vc-gW-0ixGJ358TCSbKgqdBv~g~f7yAAAA
|
||||
i2pcvs.i2p=okyFe1gfa8R5CRoUAiZA105EaZVuLV4V82bGvd~lRhVhDWGgGMzLycW6XTjtlSpAJOQ1sTox-91EjyFcX4Z0lQKzFGNJrcEvsGQ99qtBJefkOnHEUNlsfo096MZwzwuywB3x3wkEQ3R1~kKjazw6YBek0I4~UkFd1J8TlSMdcM5K0WQtjeJyMEN5NQ-YZupJk7gth1khdtK2HXphFYDQkkZaKPY9ZLMxDtHUgwEcDS5KXfnpLEssakAd0zRPS03xQ9hzlbRkjNAnc8FE6Vkp7zflnOFo4qglNAhJb1IBl53r~Gp91QeD4WY0mZgkVb5fbsF4b3F3SerutnrjhLqO7y~TUM0Ww1pYXD2E~aIqdSz46cZ7AEETc7KZl1BtAQmKQFeJiPHn4xqLlRsMv-hxB2pupthpZ5nLhaOr738AuCJVdrCAMZM9jw3TtUZnckq2tPmA6IofFNIHAdQCQTxee5EXvakTC0Mw6-MHVq-OLuGDP9anEK2IFeZolDIU5pUAAAAA
|
||||
jap.eco.i2p=R5rLvDIz8JAHgR7tf6iC8z7-ugctL23ipkNr~3SNCTxzdggCndToz~kmVACG5mGaoNf--KZsIKM2pbgSci45cJ1xAipx8CAaC3bbP3paDAY01hnxBPXcMN-Q1bisJPQHjkCphLRZqIh7gb1oXxiTtXU7erqkdAPXg6kvscFiblv5JxRg4B3Y5HHJ91UEb~Bc2oIsESQX3pCK~zmhWlReYROnoenaa9DDP1b6Na3SOAhgECR3PjoneMco1qDqlndb3C5wJ7X9YDGcZaIE~N6c0erNzWX-mGkEGARVLjyhtsOHMC72CROehAB3tsjwneKFojUpGQ16xcHGX4hEKFLNPztAbvnT7fg01dfcQb01YzIuBxbbjsP8nnoLJbEpjOaRrdIfk0cXm3aE6AQt7LoKYA-bW2sxgBxEAKsjcj4rQ57SDAi97FjnvzH2Xt363eHt0Jf0CypyEO9ajQYChEeMj6ISyVGe2a7wNJutq7aAx0ilbYB3Tiq0ExrbEsmXVvsOAAAA
|
||||
fillament.i2p=Pa50z7pU~ni5nWwUdaDZ5CJxG0fYjoarm9wlxnkgX~wHMX9RPgQAXz~r0Rr1Nadt2OA~dr9RMHswrMok0hutK3JZuFD707D7FjmWW2w979Ee9I3zxKyx9W5A2eE49PPT131NLa3uINXLXOYVA5frfDOmM75Dmvm533r8e2kloemJyj22HpvRiSXiQYgqYJGDMH3Hlnwk884eRkQu7P8DJL~hcuKpyY0FzLZtfxTNsdSavGjl7rKPMzJeP02-9TS5TkdHokZrstVM5Cn9ay1c8DQrMds7SPXJy13Ut34QRjb65JxRV0mrnY3teXewW6QFvFMXJCsf5C3i46t-9Fufy5D1H8cSd2Tx~Xl71MC5-1AJCcIS01Od23E9tFY3dU7IOSRhKC~FiAslyk3x-BnBSpKxbgl1w~LArBm5plNiCiUemJU88xYdn1UyukLer~yNrEHAWspckCRkXFwmUtPkaGNTvfwBSYns-skNHSd7MAUUoS-ewStBdmtnDgRkwSG9AAAA
|
||||
eco.i2p=KhRG6BGxVPh-BUDDfIgy0570cppTdighytcaGVR0HzQo46tgRMBp9Shlpax5FQX4nLHn6qHQbdFFpFbAwe7CiDhURCVF9-CxYmPurGadxlMPHMjz9O3jHX0CQiv2iULsk4XPrYXF3PqBc4t1J6vVyBVO7uTUhDi0gF6sN1Ro-1GLcWcsoR8Kx-hb~Z4WqGD0QAROOBPHnSRSb236qVBkhFvSkfigfBq3jFgEsttadYJA9ZLSUj1XrFFRBjz~xkRra8kJQSZl5dbfg-eZMlL49h61U6Uta5n1~tL6sarmnl9CaTl2Qo27SKB1OmMLeZEteA5G0-~LiOjN0nxaKpwrCjKIOyvwbQy2QqE-GEb9m8SN8nC2bwYK9fH15pTMHY8GvPYGcUukbF6RhefwzkEJLZ~PaAECrZYuLsn9KE5C35uRnlWJiuJlJ25hG7da5tFMyDB95efzq5IMxPeI0pMigRfuVfRSaGDpNos6JxjfEIX8umk3jIJUPhz1d8gP4QgrAAAA
|
||||
aum.i2p=09hSo56PTtkFLUEt1iUTO7zYTnO-B~ogsIsyyPWif6q1Iz4wz4JoBflAWZtedPmwGmH0nly4HYUS0gAADoUmBUnXwemmO6dxT-hPQkfEW-A7b3uEvYQWIN~kyFyg0Pa~FN6TaD9kGFttBN-GE4wxiHhXmWdzWNDVb0q5PVGnxMm6Jleik8xkd2Lgeexze8rIv8LCocAWx074USVkbCVQwoFi2P7EnjLq8odSz1cJAntbuCFeUZcjbslE3qmlcTFMCNCZXWKVzn7d5m4oszCQ83NidgekwxJ-S~iS6mBwIS0XKI--4iXiKXzzCFf0KtYfEWpvKCuqNJOcU8vQWAA2-i7~K28aLPzccDQn7acXWLKRTXF3tf0i6e-lSx-X6WTSWK-fuNitjAtKu~jqO10d~bCk7y~UPL-XwdH1XSTbk-Phhk7UoBTDiHY6zQHdD~iAzXER~9JXsJ4UoIrGFVabg7frzSt82CN7Ek5Li4AMz5gg3wq9H9HUa7xM5QfGIJpXAAAA
|
||||
mp3.aum.i2p=vBOu1caCAajaL5WRMuwk4LwfXrlcn0WzA6iHUKV5ULhaBpJb9pR3SZpnQms2Ot2c5Fvu5I6Rp7WF6QRcyasAhUmC915ap~2~VG8KCDP0z3Quh1-eqGcmzErsIfXdh09j3CWuxN~fH84hd~KswqGudFkWtFTM9RcuQUGSC1efG7uF03uaDI-DKu7eb4VUV-hmpXb3Lqntgo5qSMBMmjyUND-f6RBoXnqM005mUZJpMoYfsBhnUEq37GG8u6P9T94nlMmtz9R3gNURpBJJKPlnEqCBN4mlE5rwspQ0ovxAlogVMhSCpQ4jr6cyWIbNx-nMzKGDj~hMQ0ndbVnWw3EDC3KsPnRnDv0yVz8Fc1YpoPwerHej7VnTupDKxc4T-j8XNA1dN8SfPmaKYEPfavlmy7HFAGcsbmeRZOq-PVvlDdrKNflug8Ysodd5XkDbh7y2k1pRDjwNBQ1EgDVAtL05-i9jerqekHkbFKJ6DlT76f06vj1R2v9qlQzAYjpcKbI3AAAA
|
||||
ogg.aum.i2p=wR2ETKWn-mxsTurWwmSujvjpOiIjLg5TsldFUa4YFTgiRZdFIB-bXuK59shfnchlEgAZR0IR3~hH-O8bZ~j6wVBdZWq7bGTmyTxQ3MeYPdqK7wH7Jp147YUabFlqJkyI~DluwBDylJrIUyc2qw~ogJ67x-KyzIF7JLnoCC4E-T8Z0vmTAFWSa3XC-ncghrdZQCqEXaCMlG9PN~a7dcDq~qdWoNoyFcgLd0IQfE8JuJ1wSvmWUNEd9vkB2Zuu3EoSoDv4C53Fc0YhVACNug~VEEL-ZBGcCBcpVNud8dOMq-CbavkD5yKqHlvq~uzRi6BY5ajHI77uepJygkHcsm-8T0PXWXdc5ib4TtUI03tPkTar4Y2iVocY~oLk2jh7pQKZNioHJT4StWv9Pj8EWaVX4-emQB5kZOBwZItjo~EAGEoBT14NSM7CmKClgc6sg7fpvWF~-cNHkZsurBndni~~FKmUeWoO0FRQRF9Ao~C1DOt2V9oBbEW1~n6anjL5V~IyAAAA
|
||||
fcp.entropy.i2p=jy0D13oJVmxSa1MltstV3FOfA5e2WAEEZJiYOJIZSUOcNnAkaR3ghE-AX4vuqyQPyOEUydpauD6cS8vfx4iZkb2U3ddlLcOU3YrFKdLrySpGtD~V126VO-9nOJFwDQOOaKAsiVGRKtMPLC64GkpU6TWSIhiVYWb7WmeAHXLLlR71DtgamAxEIlP3VhytxlS3vuvAoEH9ItsBwkv4N~7jec60WMQINl~c7uDDsuzKFY8wQlkHnLFQJCQ0VExfNYqK9nZ3x8TXNPmNKTMMQ1CUCowgwR783U7UAYqsxNrpkuWvTleadn7QcR9i2v4~L9zOeHd4nHBy8PAjO29g6nf6DIsYhg4c2HYnPYzktQ1NIElytmW83BhbXJXLgNBs1eI9gDaQmOiXi74FMgfg63IcXCYWeqCdwEzSouSphaXEHDcZZVTx7DE9R-1Bi4Dt~KvPOFsAoOqsjHCpHq1gS0u5HiL0hkSm1I1EMk4JBY0j4rM1nAt7e0ix~WiOz5jXlTVSAAAA
|
||||
http.entropy.i2p=ON2Ud-B0-pJKbTR0Obpjp9wEG8grUpu55gEn5Mz3-dkVkPhHvHK6iLasr~P~Rf4kPPZvn-eK7z6rAVfsAytAJ9pcTH3lXERTjkd9FzVJJ0twbZSQ~XzX5d-24IPIMf00KegjnDkRJ82cRMKa-u4H-ayei~Y7xsSx64zC1eHv6qFxavtql3zRrS~du41~EHtpjqOtOo9Ea3lfFjhm2jUIJpYyVHqve3WbTfMBlguVALwGZIfenph7oQ1Hx~OnEtaviWuOEpupjm11LS9xqCNsccaEpJGvGt6ijxd9hrEuQZ5Ja~C0fNxf3xNtgRaUhakA8Xoo~jz8rCkV2vYQo58kj0E5xYrUQczomj8y-eDBZyq29BP8pfe2G1u3hpHA1z470LUeMPk8qVx8Cx8ZKmSK9XCvOl7WCnFS2~UUfzxbxSxPn9LfzxDZp05AVi9t~hJg-zkrL2n1wfEnScuUFapxarwK90rlAxNSnau-K61WfcXqyVMwDxl3leJOeVdHqhpbAAAA
|
||||
www.mail.i2p=Y~V8YK2M-my6-Gw0lkrkJouxeqPuB03idp-4uT9pkIXCA5nki9m4YFfPObSPv0E7c2shBxwlUo-6beaRQ-7tCawJssDRc0C0PhRj12QUYYdtZP7JS8SQXy68gZIylY-wfyEXleIC4mYY5mSthhdUUfyo1lqzrdHc1NpjPBxRJcyMBFBGUeM7Of9E9M518jXpVl0bAmxSnr5dy7sgKAVNufzfqIBfEHnmL2ZYH78FoGnPybsV0F9~154emkmt89ZUbx0BuYvH3kT1zin8pSxKw1NqxvqYt7p8CElq1--U38rO9U5Y~kLB9f6F3RYJdkl28ANkvdgJUgqiHLVI5oPWATrJLAOokyGKhK4Xl4Bjp4SCuemxHwTOGyd-4Kl8cO41u3w1LksndX9stkV6U1X0gL9BeSIoa1997IgMLVbUiDMyCz7-cA0y2tc0EdQdlpc2y77nTdo7z23dMSJzWDXsrfmLhX7M24D70htLLc1dpwZ1BUEvM1uPqGfsBSrHdl-sAAAA
|
||||
smtp.mail.i2p=gfSAYcvEsuU3oNGqeMpq1wZqH-whE1i~YCXEDwYzp8LrmukWsndvPER1~gF5QFrIp2RMXiietF3zEPtAJgevSG4ULxRU0s9MSAMXXlCACVhlf0m493J6kIYnkypOPC-Z8sulyF2kXM8BURLfSH57SS2uxLbx0hkc8j0kR9iys3ksxm5dgW-Rs7clAvLmmfASJkXZiU6DRhWbW84GbpAi9McE3ORhNLrWV5t1W4DXqzT0tzF2W0i8BEGns8XdOBQei9RAewzo5NRGPBmUl6ZKjEJ-2UtX189HPs7FcLknfsRxXhRcPQ1RombPezYCgcNhOgWY9owHq64mwGaDCnnpDSM01sdAuMlFfs1JJMoYNrILckjiHUNzV2XS8A0PIWdO4W0cT1EUs-V2a7Ocvg397HpR6Z4k-7fOrjs9yvpFsCPIEKYUD0mjr44N5pJIc61GGuNE~2ihQZGA3ju0OnUKTRZel3nK0rxl-qfFXsBsEB5vt-MTvKS73ZJdxUKWWzbWAAAA
|
||||
pop.mail.i2p=aG6owmzirq7QZKYovpSVa4-WBLfI1uJ38cNmb6kkSkcS8A~JdoLWPj6eXieN8r3m7YJLxxyZhf3urmK9qJbiIPBp53M8bOSSkldpFz0NkQPWUWmYXiOrEsOBlugbJ8nNelDcOebqoieKBOTaF-WPJQil5C6RYdUy~PL50O6Qp-Hog1868zP26leYBBFiyzzWI3bOpOsgV~4bNXnqKQZeHXz1Ua2DkV-vDBpeamPzvNWQI6cNodf04PBKXK~TlduLZDK7v1yTt2LhPSBM5nE7ZRtS8KQIdh4o-nyYRmHjA~OQ70gowGpmsRqXHQxOpPro~C7w3gSe2N0zhqSHKstwoFJD-NmsBQ5opyOiKccATpWEQdAwmoICD6rw7TGG4XYXCtyTD2xxLffER0SEsJ-BJesKKhdm-qyEMAOQq00jatoEs9jBYoujFLFQMUgaDejRJdEHWkiGT~x4auosHGYavmrcm-0mdX0CWYgfjwVb~PORhBqHJ1G5IgRPjoZLuxiJAAAA
|
||||
nm.i2p=UhUVbM972VwQgqS28SkGPtghCb~IpdpeMW7O9E7I3HtlB2I3XGbMeUAoya5RHsoG3TYxf~P6lA5IM5Z~mDZlcbZ~AG7255FE6Z3Jl9kfMArneou6AYaCfNBqNTRS-P7yX9s0Kss-vM63yBulxhS7CqmBKTZsXR27PNjJS0PMYsWBzciuy8UqUkE0YEhSLWSUYAXLP9FKs065CNjxsLumkkoF~MNUaBNEmbCrjpv5Ih9vrwz4XAJSE~S61qSMj6O-nvEPDVhTJJ5ymeoZnYMpIRt4r7FkCTH8vYSkXZkhqXUkLC11WPC33lw2wzh-irmIb5GQeab9o0-DuNQcvUnbK13Jxkq5XiilfK6kgKiPcEniqxMb-4paZAl8dj9Zp01LvhfjlS0c459Jv-gr7ZkjkX7hhTaEVvqwPyFgoVKnxQCitoZrK98WRKJwu7EQb-Kin2vzUsYfpGGI0aT68~gdr23oom2FsoZ~owVuur1h0bJr9mnCaMf6jaioQE7wezxgAAAA
|
||||
mp3.tc.i2p=LRCWTiJouI6ohqtaVBNHWZe2ymjhtyj3z9KdeI2G2D9l0cYFsG0CRUVT5VPYOg~WykALRVBiL-2U24fbiQ28hhPdQgBMBDl9aQiZJM2hv~di0uVOdARhRSgCDgRQAWioAfpXeg6pyklzXU3TNLY4c2CRLe~9Y7wuLbK3lONsAApcxxKeHLrfGNkZZwJTKd7PcG78KAHU07E-TVNf4tQrOh8tSrHaMB1r7cQQv1Jl8mNUZWz4fGeNYEZ1wr04w74Em2~Z5K0VZ4mH5DhFGXc9ALYzZf6uOVzZKiUC0eOcdfGNdVUbIog2CYJGH69TgAX69d5vF~kEzvHSzX7RxUTt0y25Rlbi6rHSDF36xOfBrOUVnSPn5X~TdKLygz~zusYpRdGZwlsyOTKVTzJHKlU6Vp5Dofijj1bUwXDQ59XCpFUqEDA17nETiOO0H5jfieYBPS6Ke2cFTAhutSvaw~albCd1eV7RPqeGdw-vFfKoucDIEVUT40B5qalyFRKIxx3lAAAA
|
||||
mesh.firerabbit.i2p=BLt3pciNQcVIU-IGnTfSsrputh~b6drZpc1vH8qeA745XoE~nMCGtw8S7HGYX4uEbwk876nQRPV4qRwGtWWkWBs8BX9qX8NABoXFk4G-6NifB4TxEizC~ZAnXZ2uFs~nrqodhyCR8bBHJL0tzBYK3E36zc~SA-DKqQ9XSHWp7ScW7Z3cdQnYKma~x4u5eZmcS23uie3OIfOCk6pJOabtaE-YWRa1eUizhucI-ysm789GumjTo858vHR1mTQfrsPTqNri3yz0aIe~w06ifciq~UlNjVfx89lLEso1vmg8rfTQ-hwxS-qz-u3K5x5vtqdGp673vCvmEnQpU6GEycmkqoLCho9pNQzGbka-OVHg8fZqlFMeBfj2iLz~zlv170jvX6HTlMCNfBYnFqavs2RQJj7--dJ0g7JHReGMKL~TciQjxljrV5AoN-0afRzTZqtDg13PL4tltJm5U1~f-GcxlsjKLZAlv26LlZXsvTDU5plldsernv3fDcBev9UaKYCwAAAA
|
||||
chess.fillament.i2p=8xvXLwcBYu2MxqMAoG6DIahvkNAwBQs43kdZTg9SlouzC3JSQ25RHvspbrxWoGIVAlB~XCxVmBB6EolYEHoSZv2YCziOj4UVxEbVP7nHkh32-7Uc5T7xlcjk8-rsmZzdgz9NhxKVn2Uhp3xtcdVAiyG4mpXisvK-7NgHY-mXPNvj6goaH58roeDUZ5otJN7nCFmcRAUpaUk-nlQs8YfgSCCEsWKOWhsVnAaHwtqtDlpdTo1YKooACMRSa-DcV5W75Il80JEWBD79qpSAeONGAOMMPT~VEMwNNg001VG-UZbvyspZdxHaETw2yd7N9l3-mwI-sYcveTTnNXLWO8IjdgDLdUIP5qrIW6WS9XZIHRKqT2kpwEw7xsEgCA~qSNiZWeai8n6Zs0rLmdyeZeafVEEW9vr6CKcLZ5W7i1SMDqCKnzSbZdd2Hvpb9aWFf5foEjLt33n8w2KSaCUe4zmN~xuQMq2yiB-vQ9~5fgPmphlMxo3ca5MTCfbhshw5137YAAAA
|
||||
kaji.i2p=i-nivH~36AgabgzvS06oKHR~MLoy6NA0oSv8JuNJDLZB8FXEDzIiyzWISmw9XJm8I7QqZ1yFH8~xe84STCHHvMMIQQrfBmOUODLWbKZor~~KhiHdNLtfVZC5BpnXkBCJkklj~fMYSpWa0C~pVRrZl75RoGtBjDVP9P8hioTv5B6RC86g2ypBH5r093rY0wnzxSL8-ZuV3F~H48VYbqro8cRlbMmjx2oEsSHkDpQyjCMVkIYKaCijkSArqZTG~zX6op6Ng9CJwdrkjKSsbzjV6MLnE4aNv-jm2WaGGD5pR24h7e3ImDOGAr17tXRtmNX5ZEQ1udQp8qIhd8UMUumrnm962r8KJWK~9WNzcVeqDrIxaaxC7vcQmXxoPeEW2efbH0yKhVZ7OFu~I9cAapSe~aNWp9UK4URSpuJvOedt0axp3ORaaM-a5U7noW3Ao-HB83qfFEPU-6uUu16HNiPqCFMJiA0qODTOwHiyyx4HKQvbhjujh4mmknSbsuapdgR1AAAA
|
||||
human.i2p=ji02vZzrp51aAsi~NZ8hwMLbr1rzMtdPUSiWAU94H89kO-~9Oc8Vucpf2vc6NOvStXpeTOqcRz-WhF01W8gj-YLP3WFskbjCcUwz0yF8dHonBeC4A5l4CjupAaztBSMbhu4vyN9FJkqZUFN01eZbQ9UqgXgLWMp4DtbUwf78y8VrzdAfmUOrVn6Iu89B~HUfOAKnpIlQXyGsQk1fnLw3PzDo2PVi8Q3C1Ntn0ybovD1xDKPrrHliTK4or2YujTcEOhSBLK4tQGvouN-tWqcVoF9O814yNGtze~uot62ACGJj9nvEU3J7QPgOl~fgBJ5Hvom0Qu-yPAGJuAZa29LSHnvRhih~z~6lWZYHREBYXQ58IzKktk90xJWcTwlwRRhyO-Sz3A5JYR3jM97h4SsoYBVrjK9TWnvGKj~fc8wYRDzt1oFVfubLlT-17LUzNc59H-2Vhxx8yaey8J~dqdWO0YdowqekxxlZf2~IVSGuLvIZYsr7~f--mLAxCgQBCjOjAAAA
|
||||
lp.i2p=i~VkqY6fes7yCR6Yn4Nowuo3h621nKC9yvMVOEGW6nC61kLRKS5xcr7JdsFohF02Z7neR7Nv3GLB1qTvqguU2SekCPpfzNYNSDCZZVPMy1IXegZupmMMtDXnY7dAwcy~d5hdjwtODidfiG37~C-AE2g8cogJSAG5-sgOTOcA288fu0n9qYn9lejK6T8vQYMJIgqW73K5ErLg8F9C8yPfCNwRlqOZ8xSWowpvlyzWy5OFMmhk00S4JA5TAKlw-ILQDT20qFOHNcCWc8biXjNDozMxcw~h3rq-TQtWyW0-J8ERf2tmjPSoRr7zxKTFXlP0ibQz8HqAhlt9xDKlpd2LZyI9cKvMNfmGBIrFZHckO~rHtCTNtA7dN-UheC~k2bX0hDlQ8A6QWKovTnt3yktOJjwxBQU3iL7UGXbS8M~8t9LmiEKdTZgMUnAcBLkrPOfTAKpkCLE-~yAYBDj~U8ZKJKEtD-gLDY9~slVuo2AgNLYF6bipXxSMsoCzevJvcWnqAAAA
|
||||
bt1.eco.i2p=SUHjD4QvbuY5VVTGTm49U8B~DtvTS2bwO1lREMNfJtZU6rxa4tgdqaIpUjRRrbZYLxZcgxjUYx4Bq7gcjriETIRaMy7lQtMx9zFk~XLE5baTmZeXc1~xuQCCTelnYv0yUswWSCZx7ll2a-Y6d88jvTydfxcCTNAYclT~gHnA0kU2kRHD5vgEteidiXFBVer2Ps68tnARUqURDKxTRyRnWtrxwQocmP5MJG29e4dIptcecxX8bKhgqgONzPAmYzAR7F694wEOXJZQO67ro0YQEuQmDXICBI8IwkbAt8qM~BG~JF1H26iWblWWs8mGJonwl34-CpVjMSXWVk~SC~60-xr97CsWSGAeZqrD8pLJyDGFsJ0jNFV0L6Q97ryUViakVwsHaAMlxZ3Hh7KAc6TUcJEGvuygRJ2DKeKA1wyuUDFc08m40HrRVwydn0hwzs4Qfb8hXfGHQ9-yauK6Gk-HvYFUL9qcIrOfrZGgq4xLVdgVh2wB1mIOTkcBMMti4941AAAA
|
||||
gernika.i2p=1D9ee5J04ZI7pvCwQ3hXQMpeMvbNw9cz3V2pioQ9LakwRQzfMEb9CVAeiFt-wE4HyTFWKeof5rz8F5vmIqFMaH~oMJSkCyNtwPfqiRAENeNeALHbY2plMPfqCfEFR4GkTqXnalAkJGqDjo55CokUfalEVTGMvNiv6i6dmNvwS8~0X56sXIaXgLKuGIK~UNOg9hT8A~uEGQWXwTKD3EDmECsJL40iYcT1UR9rffzuyxOvDhL12HbJ8bIlUGarzEscH-jolj5ShvZAbEyw-MnVzR9LiEqy7DaniKpPtC0oXRZuz7PpcQTqzN-zgQaLq8bHTx7NHIfTuA4P~hhz-STO4SjPot7h~Gbdglc193OmGlL5QwbfjXfdOIccBDh6~jtFaa28GxHrTMoi9GafjnllLfWpvynN1y5H7Jh7Uw3E7KDtBGVsDg9-btyvyQLP3kkqPfIAn7Oj6ePHr9u-TN9ZwDbWj~QBmXXutsE~lLu7aT7kv5Jx6PFmLEeWPib82UuGAAAA
|
||||
www.aum.i2p=8x3TYbh9aq6EVo8rSuPWBVSrwD~yS1al6z0RZYvRaQFXL9hFUUJYJNL2n3oR3tBg7Zr00MjqntIuBMd20LUKasnklTp4hDlDCE0KfeftYh~bFGykMRf0yTYEWaMHYpIRBY-IJEvSVlgHe8E4AWLMv-b6VKCDZ0~0AdUrsHQ0Qb4NQ-igBPZfU6c~UU0tUVUl1Efsuz1CdAp5pw5RdPviFtPH4tMvUca2t54Rwa-6v6QCqFZ7S2awyhAa73Zb9YlcqT4hP1JHF0wR0rL-OEoJV0gG47Co4Zr903SNW6cgKDj3lW1tIpzcVMoH3BE22SMEVjYyEHgAORdoYwaj19CUg1slDGmvUCoq4dPsnCIrvV7N0LeoUkZekt2pvr~yCH47ENV3oQYpFVLcMLN82tzI0ZFz5IyBHWGr22vlDlT1C-QVhAYQKN92XubDXSEgrhWv5IHPB0h~EgZi-rDcsJG2zb6ZqjtKFHp4CnNvTUxE1cCJh0aR1MDzM~o0iSMiMqh0AAAA
|
||||
madman2003.i2p=Uilou~5268x~UI5Cjg3XYKfBCeKVEMuD0g9Ea7~j2aNXbczbYXCdRQEetYMk0919Dyyj5Bfpiz5m4~xHMUoXs1aJedteFjvwwOHcXE1IuhORhILv8kKyW4zBYrK7LUnvn~xFsnl6~xaThgwxneXLGhKz5UOitIzDUJmxCAwfcARHgx9PIUuJ5LTninB3Luyev11TIqweOfe~X0dzVYSmemBgaw2T6dxCz7qz0mN5fJe18~qnTqJibKT~7teX~hQhRQolk38P3tLeBRq3wZGTRsJ6biGEydTbE-rqKrNvIMNYvUbhhRHEMdShAiUo4gPJwbyCRRScI~DbluYuy6iXXURnrCIEv2DKtfpHSS~JccPu25HKcpNBfCLzQye7L8hWgjAyaQoTi3idW2JJNEIBBBS4yfGk7wq9swlzBK2aanI-JzFcDrVDXd~fpN0JYN-phF3YJnKspJe2i83BW8J9b-y2JJVbTWBwU2mIEE~gXKggsCvqESfuKK5RhgFaS4dTAAAA
|
||||
ardvark.i2p=lHNIrjt2OnxJG~p1ZVJ1UzdwoCNLSAGbXq81bSKFQsMNCXvVtYA8-uJPTk~F4uYyaiH7x9SGg5B5dK0RmLdacBNCpwPlCPPxEpHbWizUahUQMo8ifLsyOS02ev4Bxf~i50RHmjU1zPUDe6LNxlFmA60zehKAkSR~pp2SJdbs-3nyBhvG6SX5CUHY5r3g7PpcGvGDnPOuGGJtR0xIqi-~89TsKK74qS6JiKv4BzqbWUn9eZ3TrSaC1-LAjoIiowC6e2Ske9r2HEl3T4fJ3qmOTu6vZWLQCBkzprAVOFoTwou8MJU97n3l9~1UrgghU939jC~IrAye~VolxqCdbsSz~m4Ol9fWdGmAfs~l4ZNgA89Q0O9Z5ooCm5LBlR6SjtOLOO2tQJstSb0V7-XpOxafp-a4EmXMEzg6f9ttBZNDW86hxLUPUzT9O90~27ss6j9txgx7lmFrDL8EjoQIFis6MNZRhqK4O7elL55UltDQeSel2E11iogHa4oZs8S7BsReAAAA
|
||||
nntp.baffled.i2p=FpTF-krVDnZvQrY3JPZ4A44P5ewaVMSppoNrfH60g~4O3AnU6Uz9V-izgw~Hrn9u10MexFZSYYDTznxCOc6MdSEME6X~hfWYP-g0zCt~OutUAyhyUiWV~g2PAD6u-SMAzxw8F9gesheQKU7drjplDXiTNMXqD9ILuAVaW4VABmci1k0mqSEOKPyZkRyJZJNbU6vx~ELytgQJqwK~LOqDiiG9~6-e3MpMJ0xHy2yUibd6xv3GRLMaJ9kWAruMM5otZdxJiOUOf7n7wWvSVDgmwUx6eEkS8bVlsPxD~HW7i~DNPa5Aa7QD0o46abx77YF2jaComxTzpFx6dwj-0AqTMRhVwtfPgiiXbzx6g28Eggyc93QFJ1wUX5Wgix3RjndsHE9bVOpxNjjDlRwpRP6ytu7zYDkyjJUXDiC-8SRIGDvcSQZi0Op7KsvrxgGt6hGeJyN4JhxE08vDHN~1S8DwqNzc5sQKkpmdJHywOBhzeeNucB3EcSvh6ZaJfJUywZ8FAAAA
|
||||
www.baffled.i2p=XchfxmcxW-Ex4IJ9Udd8b3O4eKgjJJx0FKM6fUbqqqXUQmOOoXKhzKwlKYobN3JIOE5zYXuWoPiW13a6befjYhZ4x6lky5--GwZe9IgLsmBqwsqGRHRaCqGiDcsHLdIWUJFWmHz38U0GYW6ggcgODQgxs00Kc2u2zfhOfVbFb8ckjvPOFah5iyfh4UQn8uaOl7WOCQmg0U47vSd6glHRq50NdRryGJv722U9TZlnrara-5f7oQn2qm1ctwnnm3sUjRMsNCJbCwuvsVY04axL5VXggwrZ6nDaZOIQHvzoNVnQtbT82GQvcBEda4Jr~IX7gLYiDS7BGNbX1NsOWBTr8GwaozPzFnaA1HMHYH0n-m2Tf~8O~7oqWzlQSP4mqcPd4ePbaXdV1uNXAr4-kziO7J3ID4vhG-biS4fYAUZqEs2rwUgFU1jPocl~Aoi~8alcgWK98mN680ZrT6th~w1BC~rf1q8Zg3MkE5RwW5jRpkOpH5a7vU8Lp6x1xQlLx6YXAAAA
|
||||
irc.baffled.i2p=AjR0lkZfdzmQqLofq5En-yeUYZ9XOugrcpqgA6giY1v880AnhB~xokcAdV76Sx~D3MLkXKjCYF-AMIrvfV8cRZ0XkY8FPFb-MnsnACGirQ4~WN6kEjQ18iEH-OStEeoATiEzCzhQlIaJkCLnRo~lAbsnmqMrV3tJIjdQOL~uxbZQB5rQl~50w01XWIzsjwo0ZqzPjsJ6715HMRp6hag0NYNgf8ZE7vFKkvRNubl60lI3LWduoA7BjwuqCvHP8QddYmmL1s-L0rjDB0JGxRJ7~YZlUA0BECTIxdv2Kv-QEwr2UmuSLFQiQ2jQ5U5z0tzwObaIcAX4aAaKY16mC~cQE5cltSS3ElnLrI0qhpIeTM6217uigbAO6iigYFK5sazru9YEt0jDk2gzxlTF6m5BAn70tnu5IAikzRe2NI8VnkRa7K473r~mS30CzwOHXuxlSEPQUK3AkkrQN1Nfw5dCBLTpIF6aMPqR5tracYPOEOlro76rzChnNu-QDZmOQGkxAAAA
|
||||
echo.baffled.i2p=RbdhJKxX35YHvCFR8wNklf~4uJISsjbCy4ur4hsD~0nZ44UVbjbYXHG3ng-PG-rcU8WIhnS2bWvyEWToQNRW8tDreXzwL3uqWmD3ceDVWgaL96oi8dcaiuNu7ualetsP8PilEOWifgAY21whF3NpFtrGPqt4-jXsQvoWiUtdncezbJu1BbcCc-18QrSrJqUjp3MejFYIJwBRZEu6zLRcN1Canky1957WZeIZUKvEyaEY~nVWZXbjcLGZKqHWIhBOc28ldLBI0xGzcrlUv4VAz2AMH0E1hFCu1sb-GrHqOuyRK1pbDKVL9dg-l7pNl6MzVruBAuDyusp9nJKN4qCjk01dy5UgQrYIFfBKGg7TIiyBcQQJHVbZPLMLbJf52G0EaVsJMTAg9cwNsJihz8-Xrp-WIio9odX9VNT6VwADcn8NTVNuf8wA1j389Ns~e4HWz1oQK89eN29GV2SD5E3PZGJ-0iBlk-Zz6hnca455tf9VfFTxKUX-7~93tRjwWdoOAAAA
|
||||
bluebeam.i2p=JE00TEW1khGvC~IB27LCrXcsZuNtzE6LE6rXL1Vy0D-hr-FkfZwh8AeQ6uocbsmGNagUY9w3C-y9XfZnCK3THev5QL3gh5SNk7iGMjUKTmR4HhKomYDXhIAOnkxeOJ7UrcuPc2H1n1SeKVXqmv7-ygRvVWY00QH7ucOAwjq3hRkuMn6r7bjxbi26Gd8xAQfjCBVvCyQWCAiBHKpMJZWSlOjWrUC~-t0uCQNWGxcskvz~todIdv3ES2XZhPVrYpYmR2mP2K9ihH1L~GpwQYM7G9-LfqfmeHA9L2i4c1ovqxPxfHwMFIevjtIBUPYZ8d7t~jkfBDjSNvA-pwIFvLmqkEOYfs8G1n6ER26PUEtp6JRfa6RbOFPcAK7WRb1lV1cVvGYr6Sm1~GkoIw5SJcFspKl~9UV-f9H5737W4mwXLvvBJWwFz9n~x0QEnEO16rnX4YVchNryTkSqcSingSQZdPQvYnHp9esCi~Mp4qvj4InRX~pfiHYGzJ1a3CPiNb2JAAAA
|
||||
www.janonymous.i2p=ovdbTie3mEme3kkvCsXjfU2BzOB0oPv0UvQLoUDSUjmPHipugKXZFYdBu0ySbl0sbJEyEgUQxDqywDq53vGtJCjqb7wDa4~hicc9bncQGbR~jhppFi2LneXRW5qawilTNP17zpyISSleRPXPBzFO65n-Z2BYz8pBm9gx0gB81UanojtE0jvAap-aYySMR6-qka44YOnEgs61fuo-~VLTgcDae-4tS8fB1PPgoLH0eqR6SQV6j0hNB9oXY4SndNtgTgCuJFTA6rkZyKdf5r9BuWybiFMvLJa~pXqSAbLefTYQldDb7LH5KQBAS2DBM7~Ri1yc1JPf-8MqWRDfi1povUICgF9kYOF7qYNaxi2xPPpqUHMiB96nUu5Lrmrecjb13qBWLDpOhiuuWconuggLo0~nFafn~Wh1yj7vCbeFfAwYaN12MleG1pzjcb1kX2rhZ-I2mVItv1WRsIVrnGiXUxoN5ZJoymkWGB7P7KdA4iCZ~CHqNLKLGQxSfblK-3XKAAAA
|
||||
nic.i2p=IV9bI7aaeN5cHSqgXe9df43lh5ZgWegT50j9FbZSc3xKFQYKKrsKYH8f7LCvzrkMGrRAjSw0yRQtRUvnTn-2lpLG4DMs9ElWHVzuIgQKL6hqRoMawcbQxth6KFLs-pjGAFfLqFRqOeY1kCGOryhuhQ7iJ7iYQicRVbfTjSYDE5Fx4oi5LbvIXL6UfcuN3SkF75EPUHZ910dKukWst1ahwaOYj4U40bGuebzDUd9Aha7tgvE~WJQ2lMLhEm6~wOg9zsDzaTL6FI7XgfREEU3XO~WOgpMJBRJT7pqAqYBK-yCI5DhfyETWZMVOuoKUhlKt78squ8B8XHlXFgpaiOE9Q1vxQWYNn~UaAjUNbeeyC1iKjj5wIlzT56G5kGAOlK-6hIPs31I02TlKABTDD-BzlyyjyBS32Lkx-5P~Z1r~okn4PFiWDJuY0idmrBNtd4Yrqc4JkXifD1evgJ5042cV231WNBbjo95jv4HICV8WaiT7LxYlkHSWqzt~SI5017MeAAAA
|
||||
reefer.i2p=OgiTTgA1xghkPcwgGpnyIJCBG1cZk17UKMAJ7H0YiroAvrAU4mexKPkWoy3JFmsenJuHnydvHJLaVbfGfEYu9EIDPynmIfBdM~WzM3Fe4IYtg1BxNnPmkz98p7lERcXiZdKXgyKPXsMta3kyEWNLrfKDwcmC4CUphE2sKjM7J61uxriCNseXaahP-U6c0aON8AFWnW5nJzF60llQGdGibKsUXUJCC5K-kphU2TEnRKMSaoNOa-QDzFsWxc3PhTDC1sRcttfsYDmW3LrXBsSaVHxAUdPveDCfKHQDoMCWN8rTBLN2GcOrqEKt85CkU1fhACt~g5asUYEvPeqVW3XS~AysfJmWWdVFdSCtQ2LkGGt1CZ9SMDThs1kqiMinly6Srpcj~eUZbb4iyumcfclmiVotLGhAkm8JNwWXJc8yfTxas1GxlTlmTaATt1J1LZS~WRTykILPGRY8HEQNp1j-vFgpXpI7xubNzPGCHQWAkOTsWfU8NvyGnXk481O6uRS5AAAA
|
||||
ugha.i2p=318iVBG9JmuN5R~ClMb6rkWqkKoCZ3yGvD4dyIJAZum4GXFMHKSLrRiBf~2tFJ2KW1Lg9tKeyDgTC6sMt0aEQTcmzZl48BsFPZlH~WiI0JnPadihezDowBSdBhMi0RXoa3~xbfOKgAsHJv1zjrfRJHYz9fgG9bNQv9~oLeKz6YRdi97yrvKZLuzaUakYWfwdk7t9ZVhaXxsW8USXLSeHfdeQb0NYmsNc6is7Gp15HvsVUMgZXuBGea-AzMY1SSA6KdJwwPlXFvvcTM7neZKJzFVXyFsaOMEDjoUFfEC3tq277H0cqv4rAVmYp7WN78oBC7JgjfvFtXCY1r1l2-Qh1AEMwevVwI7tWdPUp50eMKEiccaHNZ7q2Zt0Uk0vlxKYW84p8ZTvXqfYDWyN8DQH2NKcy87MV1ZapDrbJSrF7cb-LvZy~nHtx~UwdKLS2gziM25JTtGiC1litArYS5KaY8rQXtL9kkSx9J66gc-05S-nbFMN-f-rEO9Fl27RvdQPAAAA
|
||||
tor-www-proxy.i2p=9bZhTZvATJzpBa6UPslEwJCiDcsNhguT1mwbayD-rY0TN4Igj1PqPeVranzoO4Ity87ABVu2XJXatMzp~xHHtNiH6hF2XwJlpzx1Rr7A7SCTZwfBB1qLglwEBqYNEV5WT1faIDaArLc6o-ukhrOkIa8aJdaEpkjDkHjPtoOWt3nIIYlDxIliABrjFxPDeQZcOJyw7ftckqWfv7RKjdC8YpZrjXuojmi-TuhdRPu58tCNoH3j9laG4fUuU1RPK0YEj1HNSRHJVHDpCATtgHHfPiQvYK2HbMb0UBUfCtccmYqu6Cft5xZGHEOKvrMLXeEryV9ye-aczeNfBG2KzaF9pgQ2AN~eKBW7~UaNOQakyIaHDiY7aZ5qOCNW1CjuyJsEkjgRvqHogh6k5d3CkP82VlbEpTl5XaJFuflNJ0pHqZq2Le2T3wMkyECLbR0cX~qifE6Uw79AJnu-XEYQHFvHb0tV3XY2STDulgZu3fqZsjOVw2lZMHPHsszqlhiDhZIZAAAA
|
||||
jar.i2p=xPIYObh2AirO1xoWCj7Wwc5RsGmQ3qulIAOHux9pOm9tzErjAfxv~2EazsZjyXCZ0zi3ylUjxqfj3L1pWEQBM-VM7HshHwg-PTuGWcdcUSRRFpQ7Gcp9u~~I3HSLRdHDj6ZkBNBk0jXM03tSKQEE4V1eum0tJwlOhBCNVqtt~FhyOBvo9~ypv6zW0sb6I3NppTYGq1LL4py4KrHSjb80e3Adfyhl1E2TfSHv6Uwp8qB~a2ac2IGhB2s-FK4gKSolpV-cUsn3roZRyq9jKJ2ciT4Y-PVcIDl6D8kV~LcNUbqD7vHy1yQxv1ByCCyIi3IYDacl5n5udwHO64L0M2mtZ1zWHS7K0~IxZTyk~mpO98qslbRfQqbk0ohZ-JGFhuB~cbVWlH6tSLmMJqmD-rOWnuxRfHLVtnbQstObQ9~KVIaX9KeLusgna2ZOhFjcy424BHtaVnbnLVyh-DEq~LkJGNx9Feyi6Z-aSQvThuhyE-ALiSvSw05x2G0yM9Me6MOWAAAA
|
||||
|
278
installer/doc/COPYING
Normal file
278
installer/doc/COPYING
Normal file
@ -0,0 +1,278 @@
|
||||
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.
|
10
installer/doc/readme.license.txt
Normal file
10
installer/doc/readme.license.txt
Normal file
@ -0,0 +1,10 @@
|
||||
$Id$
|
||||
|
||||
the i2p/installer/ module is the root of the I2P
|
||||
installer, and everything within it is released
|
||||
according to the terms of the I2P license policy.
|
||||
That means everything contained within the
|
||||
i2p/installer module is released into the public
|
||||
domain unless otherwise marked. Alternate licenses
|
||||
that may be used include GPL, GPL + java exception,
|
||||
BSD, Cryptix, and MIT.
|
77
installer/java/build.xml
Normal file
77
installer/java/build.xml
Normal file
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="installer">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../core/java/" target="build" />
|
||||
<ant dir="../../router/java/" target="build" />
|
||||
<ant dir="../../apps/ministreaming/java/" target="build" />
|
||||
<ant dir="../../apps/i2ptunnel/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" destdir="./build/obj" />
|
||||
</target>
|
||||
<target name="jar" depends="installer, guiinstaller" />
|
||||
<target name="fetchseeds" depends="compile">
|
||||
<jar destfile="./build/fetchseeds.jar" >
|
||||
<fileset dir="./build/obj" includes="FetchSeeds.class" />
|
||||
<fileset file="../doc/COPYING" />
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="FetchSeeds" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="installer" depends="compile, fetchseeds">
|
||||
<jar destfile="./build/install.jar" >
|
||||
<fileset dir="./build/obj" includes="*.class" />
|
||||
<fileset dir="./src" includes="*.template" />
|
||||
<fileset dir="./src" includes="install.config" />
|
||||
<fileset file="./build/fetchseeds.jar" />
|
||||
<fileset file="../../core/java/build/i2p.jar" />
|
||||
<fileset file="../../router/java/build/router.jar" />
|
||||
<fileset file="../../apps/ministreaming/java/build/mstreaming.jar" />
|
||||
<fileset file="../../apps/i2ptunnel/java/build/i2ptunnel.jar" />
|
||||
<fileset file="../doc/COPYING" />
|
||||
<fileset file="../../readme.txt" />
|
||||
<fileset file="../../hosts.txt" />
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="Install" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="guiinstaller" depends="compile, fetchseeds">
|
||||
<jar destfile="./build/guiinstall.jar" >
|
||||
<fileset dir="./build/obj" includes="*.class" />
|
||||
<fileset dir="./src" includes="*.template" />
|
||||
<fileset dir="./src" includes="install.config" />
|
||||
<fileset file="./build/fetchseeds.jar" />
|
||||
<fileset file="../../core/java/build/i2p.jar" />
|
||||
<fileset file="../../router/java/build/router.jar" />
|
||||
<fileset file="../../apps/ministreaming/java/build/mstreaming.jar" />
|
||||
<fileset file="../../apps/i2ptunnel/java/build/i2ptunnel.jar" />
|
||||
<fileset file="../doc/COPYING" />
|
||||
<fileset file="../../readme.txt" />
|
||||
<fileset file="../../hosts.txt" />
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="GUIInstall" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../core/java/" target="cleandep" />
|
||||
<ant dir="../../router/java/" target="cleandep" />
|
||||
<ant dir="../../apps/ministreaming/java/" target="cleandep" />
|
||||
<ant dir="../../apps/i2ptunnel/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../core/java/" target="distclean" />
|
||||
<ant dir="../../router/java/" target="distclean" />
|
||||
<ant dir="../../apps/ministreaming/java/" target="distclean" />
|
||||
<ant dir="../../apps/i2ptunnel/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
7
installer/java/src/.nbattrs
Normal file
7
installer/java/src/.nbattrs
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE attributes PUBLIC "-//NetBeans//DTD DefaultAttributes 1.0//EN" "http://www.netbeans.org/dtds/attributes-1_0.dtd">
|
||||
<attributes version="1.0">
|
||||
<fileobject name="startRouter.sh.template">
|
||||
<attr name="org.netbeans.modules.text.IsTextFile" boolvalue="true"/>
|
||||
</fileobject>
|
||||
</attributes>
|
67
installer/java/src/CliInstall.java
Normal file
67
installer/java/src/CliInstall.java
Normal file
@ -0,0 +1,67 @@
|
||||
import java.io.*;
|
||||
|
||||
public class CliInstall extends Install {
|
||||
|
||||
private BufferedReader _in;
|
||||
private PrintStream _out;
|
||||
|
||||
public CliInstall() {
|
||||
_out = System.out;
|
||||
_in = new BufferedReader(new InputStreamReader(System.in));
|
||||
}
|
||||
|
||||
public void showStatus(String s) {
|
||||
_out.println(s);
|
||||
}
|
||||
|
||||
public void showOptError(String s) {
|
||||
_out.println(s);
|
||||
}
|
||||
|
||||
public void handleOptInfo(String s) {
|
||||
_out.println(s);
|
||||
}
|
||||
|
||||
public void startOptCategory(String s) {
|
||||
_out.println("* "+s+"\n");
|
||||
}
|
||||
|
||||
public void finishOptions() {}
|
||||
|
||||
public void handleOption(int number, String question,
|
||||
String def, String type) {
|
||||
Object value;
|
||||
while(true) {
|
||||
String answer;
|
||||
_out.print(question+(def == null?"": (" ["+def+"]"))+": ");
|
||||
answer = readLine();
|
||||
if ("".equals(answer) && def != null) {
|
||||
answer = def;
|
||||
}
|
||||
if (setOption(number,answer)) break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean confirmOption(String question, boolean defaultYes) {
|
||||
_out.print(question);
|
||||
return readBool(defaultYes);
|
||||
}
|
||||
|
||||
private String readLine() {
|
||||
try {
|
||||
return _in.readLine().trim();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readBool(boolean defaultYes) {
|
||||
String str = readLine().toLowerCase();
|
||||
if ("".equals(str)) return defaultYes;
|
||||
return "yes".equals(str) || "y".equals(str) || "true".equals(str)
|
||||
|| "ok".equals(str) || "sure".equals(str)
|
||||
|| "whatever".equals(str);
|
||||
}
|
||||
}
|
86
installer/java/src/FetchSeeds.java
Normal file
86
installer/java/src/FetchSeeds.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* $Id $
|
||||
* Copyright (c) 2003 mihi
|
||||
* Licensed under the GNU Public License (GPL) as published by the
|
||||
* Free Software Foundation, using version 2 or later of the GPL. You
|
||||
* should have recieved the GPL with this source code, otherwise see
|
||||
* http://www.fsf.org/copyleft/
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
public class FetchSeeds {
|
||||
|
||||
/**
|
||||
* Fetch seednodes.
|
||||
*
|
||||
* @param destination the dir to store the seednodes to
|
||||
* @param sourceURL the URL to fetch the seednode from - must end
|
||||
* with a slash
|
||||
* @return whether new seed nodes could be fetched
|
||||
*/
|
||||
public static boolean fetchSeeds(File destination, String sourceURL) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
URL source = new URL(sourceURL);
|
||||
URLConnection con = source.openConnection();
|
||||
in = con.getInputStream();
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader(in));
|
||||
String line;
|
||||
while ((line = br.readLine())!= null) {
|
||||
int pos = line.indexOf(" <a href=\"routerInfo-");
|
||||
if (pos == -1) continue;
|
||||
line=line.substring(pos+10);
|
||||
pos=line.indexOf("\"");
|
||||
if (pos == -1) continue;
|
||||
line=line.substring(0,pos);
|
||||
fetchFile(new File(destination, line), sourceURL+line);
|
||||
System.out.println(line);
|
||||
}
|
||||
br.close();
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
System.err.println("Unable to fetch seeds from " + sourceURL + ": " + ex.getMessage());
|
||||
//ex.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static void fetchFile(File destFile, String fileURL)
|
||||
throws IOException {
|
||||
URL url = new URL(fileURL);
|
||||
InputStream in = url.openStream();
|
||||
OutputStream out = new FileOutputStream(destFile);
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len=in.read(buf)) != -1) {
|
||||
out.write(buf,0,len);
|
||||
}
|
||||
in.close();
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* test main method.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
switch (args.length) {
|
||||
case 1:
|
||||
fetchSeeds(new File(args[0]), "http://i2p.dnsalias.net/i2pdb/");
|
||||
return;
|
||||
case 2:
|
||||
fetchSeeds(new File(args[0]), args[1]);
|
||||
return;
|
||||
default:
|
||||
System.out.println("Usage: FetchSeeds <outDir>");
|
||||
System.out.println(" or FetchSeeds <outDir> <seedURL>");
|
||||
System.out.println("The default seedURL is http://i2p.dnsalias.net/i2pdb/");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
338
installer/java/src/GUIInstall.java
Normal file
338
installer/java/src/GUIInstall.java
Normal file
@ -0,0 +1,338 @@
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Button;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dialog;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Frame;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Label;
|
||||
import java.awt.Panel;
|
||||
import java.awt.TextArea;
|
||||
import java.awt.TextField;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
public class GUIInstall extends Install {
|
||||
|
||||
static final GridBagConstraints gbcLeft=new GridBagConstraints();
|
||||
static final GridBagConstraints gbcRight=new GridBagConstraints();
|
||||
static final GridBagConstraints gbcBottom=new GridBagConstraints();
|
||||
static {
|
||||
gbcLeft.anchor=GridBagConstraints.EAST;
|
||||
gbcRight.fill=GridBagConstraints.HORIZONTAL;
|
||||
gbcRight.gridwidth=GridBagConstraints.REMAINDER;
|
||||
gbcRight.weightx=1.0;
|
||||
gbcBottom.weighty=1.0;
|
||||
gbcBottom.gridwidth=GridBagConstraints.REMAINDER;
|
||||
gbcBottom.fill=GridBagConstraints.BOTH;
|
||||
gbcBottom.insets=new Insets(4,4,4,4);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new GUIInstall().runInstall();
|
||||
}
|
||||
|
||||
private InstallFrame frame;
|
||||
boolean installing=false;
|
||||
ArrayList categories = new ArrayList();
|
||||
InstallCategory currentCategory = null;
|
||||
|
||||
public String handleSpliceParams(String s) {
|
||||
// any better ideas?
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
public GUIInstall() {
|
||||
frame = new InstallFrame();
|
||||
}
|
||||
|
||||
public void showStatus(String s) {
|
||||
if (!installing)
|
||||
throw new RuntimeException("Not installing yet!");
|
||||
frame.showStatus(s);
|
||||
}
|
||||
|
||||
public void showOptError(String s) {
|
||||
frame.showOptError(s);
|
||||
}
|
||||
|
||||
public void handleOptInfo(String s) {
|
||||
currentCategory.addInfo(s);
|
||||
}
|
||||
|
||||
public void startOptCategory(String s) {
|
||||
currentCategory = new InstallCategory();
|
||||
categories.add(currentCategory);
|
||||
}
|
||||
|
||||
public void finishOptions() {
|
||||
frame.startInstall(categories);
|
||||
System.out.println("Starting install...");
|
||||
}
|
||||
|
||||
public void handleOption(int number, String question,
|
||||
String def, String type) {
|
||||
currentCategory.addOption(number, question, def, type);
|
||||
}
|
||||
|
||||
public boolean confirmOption(String question, boolean defaultYes) {
|
||||
ConfirmFrame cf = new ConfirmFrame(frame, question, defaultYes);
|
||||
return cf.getResult();
|
||||
}
|
||||
|
||||
private class ConfirmFrame extends Dialog {
|
||||
private boolean result;
|
||||
private ConfirmFrame(Frame parent, String msg,
|
||||
boolean defaultYes) {
|
||||
super(parent,"Installer question",true);
|
||||
setBackground(Color.lightGray);
|
||||
setLayout(new BorderLayout());
|
||||
TextArea ta;
|
||||
Panel p;
|
||||
Button b1, b2;
|
||||
add("Center", ta = new TextArea(msg, 3, 80));
|
||||
ta.setEditable(false);
|
||||
add("South", p = new Panel(new FlowLayout()));
|
||||
p.add(b1 = new Button("Yes"));
|
||||
p.add(b2 = new Button("No"));
|
||||
ActionListener al = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
result = evt.getActionCommand().equals("Yes");
|
||||
ConfirmFrame.this.dispose();
|
||||
}
|
||||
};
|
||||
b1.addActionListener(al);
|
||||
b2.addActionListener(al);
|
||||
pack();
|
||||
|
||||
// java 1.4
|
||||
//setLocationRelativeTo(parent);
|
||||
show();
|
||||
(defaultYes?b1:b2).requestFocus();
|
||||
}
|
||||
|
||||
private boolean getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class InstallCategory extends ArrayList {
|
||||
public void addInfo(String s) {
|
||||
add(new InfoOption(s));
|
||||
}
|
||||
|
||||
public void addOption(int number, String question,
|
||||
String def, String type) {
|
||||
add(new RealOption(number, question, def, type));
|
||||
}
|
||||
}
|
||||
|
||||
private interface InstallOption {
|
||||
public Component getComponent1();
|
||||
public Component getComponent2();
|
||||
public boolean setValue();
|
||||
public String getQuestion();
|
||||
}
|
||||
|
||||
private class InfoOption extends Panel implements InstallOption {
|
||||
|
||||
public InfoOption(String s) {
|
||||
super(new GridLayout(0,1,0,0));
|
||||
for(StringTokenizer st = new StringTokenizer(s,"\n");
|
||||
st.hasMoreTokens();) {
|
||||
add(new Label(st.nextToken()));
|
||||
}
|
||||
}
|
||||
public Component getComponent1() { return null;}
|
||||
public Component getComponent2() { return this;}
|
||||
public boolean setValue() {return true;}
|
||||
public String getQuestion() { return "<no question>";}
|
||||
}
|
||||
|
||||
private class RealOption implements InstallOption {
|
||||
|
||||
private int number;
|
||||
private String def, question;
|
||||
private Label l;
|
||||
private TextField t;
|
||||
|
||||
public RealOption(int number, String question,
|
||||
String def, String type) {
|
||||
this.number = number;
|
||||
l = new Label(question);
|
||||
t = new TextField(def);
|
||||
this.def=def;
|
||||
this.question=question;
|
||||
// type is not needed yet
|
||||
}
|
||||
|
||||
public void reset() {t.setText(def);}
|
||||
|
||||
public String getQuestion() { return question; }
|
||||
|
||||
public boolean setValue() {
|
||||
return GUIInstall.this.setOption(number, t.getText());
|
||||
}
|
||||
|
||||
public Component getComponent1() { return l;}
|
||||
public Component getComponent2() { return t;}
|
||||
|
||||
}
|
||||
|
||||
private class InstallFrame extends Frame {
|
||||
|
||||
private int current = -1;
|
||||
private Panel cats;
|
||||
private CardLayout cl;
|
||||
private boolean windowOpen = true;
|
||||
private TextArea log;
|
||||
|
||||
public InstallFrame() {
|
||||
super("I2P Installer");
|
||||
setBackground(Color.lightGray);
|
||||
Panel p;
|
||||
Button b;
|
||||
setLayout(new BorderLayout());
|
||||
add("Center", cats = new Panel(cl = new CardLayout()));
|
||||
cats.add("Start", p= new Panel(new BorderLayout()));
|
||||
p.add("Center", new Label("Loading installer..."));
|
||||
cats.add("Install", p= new Panel(new BorderLayout()));
|
||||
p.add("Center", log=new TextArea("Installing...\n\n"));
|
||||
log.setEditable(false);
|
||||
add("South", p = new Panel(new FlowLayout()));
|
||||
p.add(b = new Button("<< Back"));
|
||||
b.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
if (current > 0) {
|
||||
current --;
|
||||
cl.show(cats,""+current);
|
||||
}
|
||||
}
|
||||
});
|
||||
p.add(b = new Button("Next >>"));
|
||||
b.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
if (current != -1) {
|
||||
if (!saveCurrent()) return;
|
||||
current ++;
|
||||
if (current == categoryPanels.length) {
|
||||
cl.show(cats,"Install");
|
||||
current = -1;
|
||||
synchronized(InstallFrame.this) {
|
||||
installing=true;
|
||||
windowOpen=false;
|
||||
InstallFrame.this.notify();
|
||||
}
|
||||
} else {
|
||||
cl.show(cats,""+current);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
p.add(b = new Button("Quit"));
|
||||
b.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent evt) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
setSize(600,450);
|
||||
|
||||
// java 1.4
|
||||
//setLocationRelativeTo(null);
|
||||
show();
|
||||
}
|
||||
|
||||
public void showStatus(String s) {
|
||||
log.append(s+"\n");
|
||||
}
|
||||
public void showOptError(String s) {
|
||||
if (current == -1) throw new RuntimeException("No options here!");
|
||||
categoryPanels[current].showError(s);
|
||||
}
|
||||
|
||||
private CategoryPanel[] categoryPanels;
|
||||
|
||||
public void startInstall(ArrayList categories) {
|
||||
Panel p;
|
||||
categoryPanels = new CategoryPanel[categories.size()];
|
||||
//build a panel for each category
|
||||
Iterator it = categories.iterator();
|
||||
for (int i=0; it.hasNext(); i++) {
|
||||
cats.add(""+i, categoryPanels[i] =
|
||||
new CategoryPanel((InstallCategory)it.next()));
|
||||
}
|
||||
current = 0;
|
||||
cl.show(cats,"0");
|
||||
// wait till config is complete
|
||||
synchronized(this) {
|
||||
while(windowOpen) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean saveCurrent() {
|
||||
return categoryPanels[current].saveOptions();
|
||||
}
|
||||
}
|
||||
|
||||
private class CategoryPanel extends Panel {
|
||||
|
||||
private TextArea errorBox;
|
||||
private InstallCategory ic;
|
||||
|
||||
public CategoryPanel(InstallCategory ic) {
|
||||
super(new GridBagLayout());
|
||||
this.ic=ic;
|
||||
for (Iterator it = ic.iterator(); it.hasNext();) {
|
||||
InstallOption io = (InstallOption) it.next();
|
||||
Component c1 = io.getComponent1(),
|
||||
c2 = io.getComponent2();
|
||||
if (c1 != null) add(c1, gbcLeft);
|
||||
add(c2, gbcRight);
|
||||
}
|
||||
add (errorBox = new TextArea(), gbcBottom);
|
||||
errorBox.setEditable(false);
|
||||
}
|
||||
|
||||
private InstallOption currentOption;
|
||||
public boolean saveOptions() {
|
||||
errorBox.setText("Saving options...\n\n");
|
||||
for (Iterator it = ic.iterator(); it.hasNext();) {
|
||||
InstallOption io = (InstallOption) it.next();
|
||||
currentOption=io;
|
||||
if (!io.setValue()) return false;
|
||||
currentOption= null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void showError(String s) {
|
||||
if (currentOption==null) {
|
||||
throw new RuntimeException("No option to test");
|
||||
}
|
||||
errorBox.append("While setting \""+currentOption.getQuestion()+
|
||||
"\":\n"+s+"\n\n");
|
||||
}
|
||||
}
|
||||
}
|
612
installer/java/src/Install.java
Normal file
612
installer/java/src/Install.java
Normal file
@ -0,0 +1,612 @@
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Please could someone write a real installer? Please.
|
||||
* I should be shot for doing this.
|
||||
*
|
||||
* Note: this has dependencies upon how i2p/build.xml executes the "installer"
|
||||
* task - namely that this class is in the jar "install.jar", and in that jar
|
||||
* are the files COPYING, i2p.jar, i2ptunnel.jar, phttprelay.war, readme.txt,
|
||||
* hosts.txt, and router.jar.
|
||||
*
|
||||
* Can you say "fragile code"?
|
||||
* (oh well, enough caveats. commit)
|
||||
*/
|
||||
public abstract class Install {
|
||||
private File _installDir;
|
||||
private String _externalAddress;
|
||||
private boolean _externalAddressIsReachable;
|
||||
private int _inTCP;
|
||||
private int _i2cpPort;
|
||||
private String _phttpRegister;
|
||||
private String _phttpSend;
|
||||
private int _inBPS;
|
||||
private int _outBPS;
|
||||
private HashMap _answers;
|
||||
private Properties _p;
|
||||
|
||||
private boolean _isWindows;
|
||||
|
||||
/**
|
||||
* Show some installation status.
|
||||
*/
|
||||
public abstract void showStatus(String s);
|
||||
|
||||
/**
|
||||
* Show an error when setting an option.
|
||||
*/
|
||||
public abstract void showOptError(String s);
|
||||
|
||||
/**
|
||||
* Show some information about the following/preceding options
|
||||
*/
|
||||
public abstract void handleOptInfo(String s);
|
||||
|
||||
/**
|
||||
* Start a new Option category
|
||||
*/
|
||||
public abstract void startOptCategory(String s);
|
||||
|
||||
|
||||
/**
|
||||
* Handle an option.
|
||||
*/
|
||||
public abstract void handleOption(int number, String question,
|
||||
String def, String type);
|
||||
/**
|
||||
* Tell the installer that all options have been given.
|
||||
* Install will not continue until this method has ended.
|
||||
* When this method has ended, all options must have been set.
|
||||
*/
|
||||
public abstract void finishOptions();
|
||||
|
||||
/**
|
||||
* Confirm an option. This can occur both while verifying options
|
||||
* and later in the install process.
|
||||
*/
|
||||
public abstract boolean confirmOption(String question, boolean defaultYes);
|
||||
|
||||
public static void main(String args[]) {
|
||||
Install install = new CliInstall();
|
||||
install.runInstall();
|
||||
}
|
||||
|
||||
public Install() {
|
||||
_inTCP = -2;
|
||||
_i2cpPort = -2;
|
||||
_phttpRegister = null;
|
||||
_phttpSend = null;
|
||||
_inBPS = -2;
|
||||
_outBPS = -2;
|
||||
_externalAddressIsReachable = false;
|
||||
}
|
||||
|
||||
public void runInstall() {
|
||||
askQuestions();
|
||||
detectOS();
|
||||
configureAll();
|
||||
createConfigFile();
|
||||
createLoggerConfig();
|
||||
createStartScript();
|
||||
createReseedScript();
|
||||
createEepProxyScript();
|
||||
//createScripts("startSquid.sh", "startSquid.bat", 5555, "squid.i2p", "log-squid-#.txt", "Squid Proxy", "Squid proxying scripts written to startSquid");
|
||||
createScripts("startIrcProxy.sh", "startIrcProxy.bat", 6668, "irc.duck.i2p", "log-irc-#.txt", "IRC Proxy", "IRC proxying scripts written to startIrcProxy", "Starting IRC proxy (when you see Ready! you can connect your IRC client to localhost:6668)");
|
||||
//createScripts("startI2PCVSProxy.sh", "startI2PCVSProxy.bat", 2401, "i2pcvs.i2p", "log-i2pcvs-#.txt", "CVS Proxy", "Proxying scripts for I2P's CVS server written to startCVSProxy");
|
||||
// only pulling them temporarily, duck, until the network is
|
||||
// reliable enough
|
||||
//createScripts("startJabber.sh", "startJabber.bat", 5222, "jabber.duck.i2p", "log-jabber-#.txt", "Jabber Proxy", "Squid proxying scripts written to startSquid");
|
||||
|
||||
//createScripts("startNntpProxy.sh", "startNntpProxy.bat", 1119, "nntp.duck.i2p", "log-nntp-#.txt", "NNTP Proxy","NNTP proxying scripts written to startNntpProxy");
|
||||
createSeedNodes();
|
||||
copyLibraries();
|
||||
if (_isWindows) {
|
||||
showStatus("To run the router, please run startRouter.bat in " + _installDir.getAbsolutePath());
|
||||
} else {
|
||||
showStatus("To run the router, please run startRouter.sh in " + _installDir.getAbsolutePath());
|
||||
}
|
||||
showStatus("");
|
||||
}
|
||||
|
||||
private String numberTo4Digits(int number) {
|
||||
String res = "0000"+number; // use four digit indices
|
||||
return res.substring(res.length()-4);
|
||||
}
|
||||
|
||||
private void askQuestions() {
|
||||
try {
|
||||
InputStream in =
|
||||
Install.class.getResourceAsStream("/install.config");
|
||||
_p = new Properties();
|
||||
_p.load(in);
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
int count = Integer.parseInt(_p.getProperty("qs.count"));
|
||||
_answers = new HashMap(count+count); // load factor is 0.75, so
|
||||
// there is some room left
|
||||
for (int i=1;i<=count;i++) {
|
||||
String ii = numberTo4Digits(i);
|
||||
String question = _p.getProperty("qs."+ii+".question"),
|
||||
param = _p.getProperty("qs."+ii+".param"),
|
||||
type = _p.getProperty("qs."+ii+".type"),
|
||||
def = _p.getProperty("qs."+ii+".default");
|
||||
if (question == null) continue;
|
||||
if (type == null || "info".equals(type)) {
|
||||
// just print some text
|
||||
handleOptInfo(question);
|
||||
} else if ("info_spliced".equals(type)) {
|
||||
// splice in some params already queried
|
||||
handleOptInfo(handleSpliceParams(question));
|
||||
} else if ("category".equals(type)) {
|
||||
startOptCategory(question);
|
||||
} else if ("skip".equals(type)) {
|
||||
i = Integer.parseInt(question)-1;
|
||||
} else { // a real question
|
||||
if ("<none>".equals(question)) {
|
||||
if (!setOption(i, def))
|
||||
throw new RuntimeException("Fixed and invalid value");
|
||||
} else {
|
||||
handleOption(i,question,def,type);
|
||||
}
|
||||
}
|
||||
}
|
||||
finishOptions();
|
||||
}
|
||||
|
||||
public /* overridable */ String handleSpliceParams(String s) {
|
||||
return spliceParams(s);
|
||||
}
|
||||
|
||||
public boolean setOption(int number, String answer) {
|
||||
String ii = numberTo4Digits(number);
|
||||
String param = _p.getProperty("qs."+ii+".param"),
|
||||
type = _p.getProperty("qs."+ii+".type");
|
||||
Object value = getOptionValue(answer, type);
|
||||
if (value == null) {
|
||||
return false;
|
||||
} else {
|
||||
if (param == null || value == null)
|
||||
throw new NullPointerException();
|
||||
_answers.put(param,value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getOptionValue(String answer, String type) {
|
||||
if ("string".equals(type)) {
|
||||
// everything's okay till the very end
|
||||
return answer;
|
||||
} else if ("string>0".equals(type)) {
|
||||
if (answer.length() > 0) {
|
||||
return answer;
|
||||
} else {
|
||||
showOptError("Empty answers are not allowed.");
|
||||
}
|
||||
} else if ("directory".equals(type)) {
|
||||
File f = new File(answer);
|
||||
if (f.exists()) {
|
||||
if (f.isDirectory()) {
|
||||
showOptError("Using existing target directory " + f.getAbsolutePath());
|
||||
return f;
|
||||
} else {
|
||||
showOptError("Location " + f.getAbsolutePath()+
|
||||
" is not a directory. "+
|
||||
"Lets try again");
|
||||
}
|
||||
} else {
|
||||
boolean create = confirmOption
|
||||
("Target directory " + f.getAbsolutePath() +
|
||||
" does not exist - create? ", false);
|
||||
if (!create) {
|
||||
showOptError("Lets try that again");
|
||||
} else {
|
||||
boolean created = f.mkdirs();
|
||||
if (created) {
|
||||
showOptError("Target directory " +
|
||||
f.getAbsolutePath() +
|
||||
" created");
|
||||
return f;
|
||||
} else {
|
||||
showOptError("Failed to create the "+
|
||||
"directory. Lets choose "+
|
||||
"another.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
answer=answer.toLowerCase();
|
||||
if ("yes".equals(answer) || "y".equals(answer))
|
||||
answer="true";
|
||||
if ("no".equals(answer) || "n".equals(answer))
|
||||
answer="false";
|
||||
if ("true".equals(answer) || "false".equals(answer)) {
|
||||
return new Boolean("true".equals(answer));
|
||||
}
|
||||
showOptError("Incorrect boolean value, try `yes' <20>r `no'");
|
||||
} else if ("numeric".equals(type) || "port".equals(type)) {
|
||||
try {
|
||||
int num = Integer.parseInt(answer);
|
||||
if ("numeric".equals(type) ||
|
||||
(num >0 && num < 65536)) {
|
||||
return new Integer(num);
|
||||
}
|
||||
showOptError("Port number must be from 1 to 65535");
|
||||
} catch (NumberFormatException ex) {
|
||||
showOptError("Incorrect value: "+ex.getMessage());
|
||||
}
|
||||
} else if ("bandwidth".equals(type)) {
|
||||
try {
|
||||
answer = answer.toLowerCase();
|
||||
int factor = 1;
|
||||
// first check to see if it ends with m, k, or g
|
||||
if (answer.endsWith("g"))
|
||||
factor = 1024*1024*1024;
|
||||
if (answer.endsWith("m"))
|
||||
factor = 1024*1024;
|
||||
if (answer.endsWith("k"))
|
||||
factor = 1024;
|
||||
if (factor > 1)
|
||||
answer = answer.substring(0, answer.length()-1);
|
||||
int val = factor * Integer.parseInt(answer);
|
||||
if (val == -1 || val >0 ) {
|
||||
return new Integer(val);
|
||||
}
|
||||
showOptError("Value must be -1 or positive.");
|
||||
} catch (NumberFormatException ex) {
|
||||
showOptError("Invalid number [" + answer + "]. Valid numbers are of the form -1, 42, 68k, 7m, 9g");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException ("cannot read installer option: " + type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String spliceParams(String s) {
|
||||
StringBuffer txt = new StringBuffer(s.length()+100);
|
||||
int ind;
|
||||
while((ind = s.indexOf("##")) != -1) {
|
||||
txt.append(s.substring(0,ind));
|
||||
String temp = s.substring(ind+2);
|
||||
ind = temp.indexOf("##");
|
||||
if (ind == -1) throw new RuntimeException
|
||||
("Incorrect info_spliced param");
|
||||
s=temp.substring(ind+2);
|
||||
Object value = _answers.get(temp.substring(0,ind));
|
||||
if (value == null) {
|
||||
System.err.println("ERROR: Could not insert parameter "+temp.substring(0,ind));
|
||||
System.exit(1);
|
||||
} else {
|
||||
txt.append(value.toString());
|
||||
}
|
||||
}
|
||||
txt.append(s);
|
||||
return txt.toString();
|
||||
}
|
||||
|
||||
private void detectOS() {
|
||||
String os = System.getProperty("os.name");
|
||||
if (os.toLowerCase().indexOf("win") != -1)
|
||||
_isWindows = true;
|
||||
else
|
||||
_isWindows = false;
|
||||
// yes, this treats pre-os-x macs as unix, and perhaps some
|
||||
// windows-esque OSes don't have "win" in their name, or some
|
||||
// unix-esque OS does. fix when it occurs.
|
||||
}
|
||||
|
||||
private void configureAll() {
|
||||
_installDir = (File) _answers.get("installDir");
|
||||
_externalAddress = _answers.get("externalAddress").toString();
|
||||
_externalAddressIsReachable = ((Boolean)_answers.get("externalAddressIsReachable")).booleanValue();
|
||||
|
||||
_inTCP=((Integer)_answers.get("inTCP")).intValue();
|
||||
_phttpRegister = _answers.get("phttpRegister").toString();
|
||||
_phttpSend = _answers.get("phttpSend").toString();
|
||||
_i2cpPort = ((Integer)_answers.get("i2cpPort")).intValue();
|
||||
_inBPS = ((Integer)_answers.get("inBPS")).intValue();
|
||||
_outBPS = ((Integer)_answers.get("outBPS")).intValue();
|
||||
}
|
||||
|
||||
private void useTemplate(String templateName, File destFile) {
|
||||
try {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(destFile));
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader
|
||||
(Install.class.getResourceAsStream(templateName)));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (!line.startsWith("####")) {
|
||||
bw.write(spliceParams(line));
|
||||
bw.newLine();
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
bw.close();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void createLoggerConfig() {
|
||||
boolean verbose = ((Boolean)_answers.get("verboseLogs")).booleanValue();
|
||||
createLogConfigOptions(verbose);
|
||||
File logCfgFile = new File(_installDir, "logger.config");
|
||||
useTemplate("logger.config.template",logCfgFile);
|
||||
}
|
||||
|
||||
private void createSeedNodes() {
|
||||
showStatus("To connect to I2P, you will need a reference to at least one other I2P router");
|
||||
showStatus("Rather than bundle some (soon to be out of date) references with the software, ");
|
||||
showStatus("you can either run the included reseed script or get get your own references ");
|
||||
showStatus("from some out of band location. ");
|
||||
showStatus("");
|
||||
showStatus("The reseed script simply connects to http://i2p.net/i2pdb/ and downloads all");
|
||||
showStatus("of the routerInfo-*.dat files and save them into " + (new File(_installDir, "i2pdb")).getAbsolutePath());
|
||||
showStatus("That ../i2pdb/ directory is simply a mirror of one router's netDb directory, so those files");
|
||||
showStatus("can come from anyone else too");
|
||||
showStatus("");
|
||||
showStatus("You can run the reseed script or download references (from your friends, etc) as often");
|
||||
showStatus("as you like without restarting your router. If you find your netDb directory to have ");
|
||||
showStatus("only one file in it (thats your router info), you will need more peers to get anything done.");
|
||||
showStatus("");
|
||||
boolean reseed = confirmOption("Do you want to run the reseed script now? ", true);
|
||||
if (reseed) {
|
||||
reseed();
|
||||
} else {
|
||||
showStatus("Ok ok, not reseeding - but please reseed before running the router");
|
||||
}
|
||||
}
|
||||
|
||||
private void reseed() {
|
||||
try {
|
||||
URL dir = new URL("http://i2p.net/i2pdb/");
|
||||
String content = new String(readURL(dir));
|
||||
Set urls = new HashSet();
|
||||
int cur = 0;
|
||||
while (true) {
|
||||
int start = content.indexOf("href=\"routerInfo-", cur);
|
||||
if (start < 0)
|
||||
break;
|
||||
|
||||
int end = content.indexOf(".dat\">", start);
|
||||
String name = content.substring(start+"href=\"routerInfo-".length(), end);
|
||||
urls.add(name);
|
||||
cur = end + 1;
|
||||
}
|
||||
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
fetchSeed((String)iter.next());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
showStatus("Error reseeding - " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchSeed(String peer) throws Exception {
|
||||
URL url = new URL("http://i2p.net/i2pdb/routerInfo-" + peer + ".dat");
|
||||
showStatus("Fetching seed from " + url.toExternalForm());
|
||||
|
||||
byte data[] = readURL(url);
|
||||
writeSeed(peer, data);
|
||||
}
|
||||
|
||||
private byte[] readURL(URL url) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
URLConnection con = url.openConnection();
|
||||
InputStream in = con.getInputStream();
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
in.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private void writeSeed(String name, byte data[]) throws Exception {
|
||||
File netDbDir = new File(_installDir, "netDb");
|
||||
if (!netDbDir.exists())
|
||||
netDbDir.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void copyLibraries() {
|
||||
File libDir = new File(_installDir, "lib");
|
||||
if (!libDir.exists()) {
|
||||
boolean libCreated = libDir.mkdirs();
|
||||
if (!libCreated) {
|
||||
showStatus("Error creating library directory " + libDir.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStatus("Installing the libraries into " + libDir.getAbsolutePath());
|
||||
int cnt = Integer.parseInt(_p.getProperty("libs.count"));
|
||||
try {
|
||||
for (int i=1;i<=cnt;i++) {
|
||||
String ii = numberTo4Digits(i),
|
||||
file = _p.getProperty("libs."+ii+".name");
|
||||
boolean isLib = "true".equals(_p.getProperty("libs."+ii+".islib"));
|
||||
InputStream is = Install.class.getResourceAsStream("/"+file);
|
||||
if (is == null) throw new IOException("Resource /"+file+" not found");
|
||||
copyFile(is, file, isLib?libDir:_installDir);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
showStatus("Error extracting the libraries: " + ioe.getMessage());
|
||||
}
|
||||
File dbDir = new File(_installDir, "netDb");
|
||||
dbDir.mkdirs();
|
||||
File logDir = new File(_installDir, "logs");
|
||||
logDir.mkdirs();
|
||||
}
|
||||
|
||||
private void copyFile(InputStream in, String name, File destDir) {
|
||||
File destFile = new File(destDir, name);
|
||||
try {
|
||||
byte buf[] = new byte[16*1024];
|
||||
FileOutputStream out = new FileOutputStream(destFile);
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read == -1)
|
||||
break;
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
showStatus("Installed file " + destFile.getName() + " in " + destFile.getParent());
|
||||
} catch (IOException ioe) {
|
||||
showStatus("Error saving " + name + " to " + destFile.getAbsolutePath() + ": " + ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void createLogConfigOptions(boolean verbose) {
|
||||
_answers.put("_logger_level", verbose?"DEBUG":"INFO");
|
||||
_answers.put("_logger_level2", verbose?"WARN":"ERROR");
|
||||
StringBuffer buf = new StringBuffer();
|
||||
if (!verbose) {
|
||||
// overrides for particularly chatty classes
|
||||
_answers.put("_logger_notverbose",
|
||||
"logger.record.net.i2p.router.transport.Triv=ERROR"+NL+
|
||||
"logger.record.net.i2p.router.transport.Band=ERROR"+NL+
|
||||
"logger.record.net.i2p.crypto=ERROR" +NL+
|
||||
"logger.record.net.i2p.crypto.DH=ERROR");
|
||||
} else {
|
||||
_answers.put("_logger_notverbose","");
|
||||
}
|
||||
}
|
||||
|
||||
private void createScripts(String unixName, String windowsName, int listenPort, String targetDest, String logfilePattern, String windowTitle, String message, String scriptMessage) {
|
||||
createScripts(unixName, windowsName, "client "+listenPort+" "+targetDest, logfilePattern, windowTitle, message, scriptMessage);
|
||||
}
|
||||
|
||||
private void createScripts(String unixName, String windowsName, String command, String logfilePattern, String windowTitle, String message, String scriptMessage) {
|
||||
_answers.put("_scripts_port", ""+_i2cpPort);
|
||||
_answers.put("_scripts_cmd", command);
|
||||
_answers.put("_scripts_logname", logfilePattern);
|
||||
_answers.put("_scripts_winttl", windowTitle);
|
||||
_answers.put("_scripts_message", scriptMessage);
|
||||
if (_isWindows) {
|
||||
File windowsFile = new File(_installDir, windowsName);
|
||||
useTemplate("startFoo.bat.template", windowsFile);
|
||||
} else {
|
||||
File unixFile = new File(_installDir, unixName);
|
||||
useTemplate("startFoo.sh.template", unixFile);
|
||||
chmodaplusx(unixFile);
|
||||
}
|
||||
showStatus(message);
|
||||
}
|
||||
|
||||
private void createEepProxyScript() {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("Eepsite proxying scripts written to startEepProxy").append(NL);
|
||||
buf.append("IMPORTANT: While this installer packages the latest hosts.txt file available at the time ($Date: 2004/03/25 00:56:23 $), ").append(NL);
|
||||
buf.append("a more recently updated one may be available. You can check for updates by fetching the file ").append(NL);
|
||||
buf.append("http://i2p.net/i2p/hosts.txt and saving it in ").append(_installDir.getAbsolutePath()).append(NL);
|
||||
buf.append("Or, of course, you can edit and update hosts.txt yourself to include mappings of your liking").append(NL);
|
||||
|
||||
createScripts("startEepProxy.sh", "startEepProxy.bat", "httpclient 4444", "log-eepProxy-#.txt", "Eep Proxy", buf.toString(), "Starting EepProxy (when you see Ready, set your browsers HTTP proxy to localhost:4444)");
|
||||
}
|
||||
|
||||
private void createReseedScript() {
|
||||
if (_isWindows) {
|
||||
File windowsFile = new File(_installDir, "reseed.bat");
|
||||
useTemplate("reseed.bat.template", windowsFile);
|
||||
} else {
|
||||
File unixFile = new File(_installDir, "reseed.sh");
|
||||
useTemplate("reseed.sh.template", unixFile);
|
||||
chmodaplusx(unixFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void chmodaplusx(File f) {
|
||||
try {
|
||||
Runtime.getRuntime().exec("chmod a+x " + f.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
showStatus("Error setting "+f.getName()+" as executable");
|
||||
}
|
||||
}
|
||||
|
||||
private void createStartScript() {
|
||||
_answers.put("_scripts_installdir", _installDir.getAbsolutePath());
|
||||
if (_isWindows) {
|
||||
File windowsFile = new File(_installDir, "startRouter.bat");
|
||||
useTemplate("startRouter.bat.template", windowsFile);
|
||||
} else {
|
||||
File unixFile = new File(_installDir, "startRouter.sh");
|
||||
useTemplate("startRouter.sh.template", unixFile);
|
||||
File unixStopFile = new File(_installDir, "stopRouter.sh");
|
||||
useTemplate("stopRouter.sh.template", unixStopFile);
|
||||
chmodaplusx(unixFile);
|
||||
chmodaplusx(unixStopFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void createConfigFile() {
|
||||
File configFile = new File(_installDir, "router.config");
|
||||
setConfigFileOptions();
|
||||
useTemplate("router.config.template", configFile);
|
||||
showStatus("Router configuration file written to " + configFile.getAbsolutePath());
|
||||
showStatus("");
|
||||
}
|
||||
|
||||
private final static String NL = System.getProperty("line.separator");
|
||||
|
||||
private void setConfigFileOptions() {
|
||||
// set fields needed for the config template
|
||||
_answers.put("NOW", new Date().toString());
|
||||
if (_inTCP <= 0) {
|
||||
_answers.put("_router_hn", "#i2np.tcp.hostname=[externally reachable hostname or IP address goes here]");
|
||||
_answers.put("_router_port", "#i2np.tcp.port=[TCP/IP port number]");
|
||||
_answers.put("_router_lavalid","#i2np.tcp.listenAddressIsValid=[true/false for whether your external address is locally reachable]");
|
||||
_answers.put("_router_tcpdisable","#i2np.tcp.disable=[true/false for whether you want absolutely no tcp connections to be established (forcing phttp, etc)])");
|
||||
} else {
|
||||
_answers.put("_router_hn","i2np.tcp.hostname="+_externalAddress);
|
||||
_answers.put("_router_port","i2np.tcp.port="+_inTCP);
|
||||
_answers.put("_router_lavalid","i2np.tcp.listenAddressIsValid="+_externalAddressIsReachable);
|
||||
_answers.put("_router_tcpdisable","i2np.tcp.disable=false");
|
||||
}
|
||||
if ( (_phttpRegister == null) || (_phttpSend == null) ) {
|
||||
_answers.put("_router_phttpreg","#i2np.phttp.registerURL=[full URL to a PHTTP registration server, e.g. http://someHost:8080/phttprelay/phttpRegister]");
|
||||
_answers.put("_router_phttpsend","#i2np.phttp.sendURL=[full URL to a PHTTP relay server, e.g. http://someHost:8080/phttprelay/phttpSend]");
|
||||
} else {
|
||||
_answers.put("_router_phttpreg","i2np.phttp.registerURL="+_phttpRegister);
|
||||
_answers.put("_router_phttpsend","i2np.phttp.sendURL="+_phttpSend);
|
||||
}
|
||||
_answers.put("_router_i2cp_port",""+_i2cpPort);
|
||||
_answers.put("_router_inbps",""+(_inBPS*60));
|
||||
_answers.put("_router_outbps",""+(_outBPS*60));
|
||||
}
|
||||
}
|
108
installer/java/src/install.config
Normal file
108
installer/java/src/install.config
Normal file
@ -0,0 +1,108 @@
|
||||
# config file for the installer
|
||||
|
||||
# intention for this file is that a gui installer can use the same
|
||||
# settings as the cli installer
|
||||
|
||||
qs.count=50
|
||||
|
||||
qs.0001.question=General settings
|
||||
qs.0001.type=category
|
||||
qs.0002.question=Installation directory
|
||||
qs.0002.param=installDir
|
||||
qs.0002.type=directory
|
||||
qs.0002.default=.
|
||||
qs.0003.question=
|
||||
|
||||
|
||||
qs.0006.question=Network settings
|
||||
qs.0006.type=category
|
||||
qs.0007.question=Currently, to use I2P you must have a publicly reachable TCP/IP address \nthat you can receive new connections on. This means that if you're\nbehind a firewall or NAT, you will have to poke a hole in it for the \ninbound TCP port (which you'll specify in a moment), and if you have DHCP,\nyou must use a service like dyndns.org and specify that hostname as your\nexternal address.
|
||||
qs.0007.type=info
|
||||
qs.0008.question=External address
|
||||
qs.0008.param=externalAddress
|
||||
qs.0008.type=string>0
|
||||
qs.0009.question=Note to advanced users: the default settings bind any TCP listeners to \nall local IP addresses, but you can have it bind to a specific one if necessary\nPlease see the router.config for the options i2np.tcp.listenAddressIsValid
|
||||
qs.0009.type=info
|
||||
qs.0010.question=<none>
|
||||
qs.0010.param=externalAddressIsReachable
|
||||
qs.0010.type=boolean
|
||||
qs.0010.default=false
|
||||
qs.0011.question=Inbound TCP port? (any unused port will do, just pick a number)
|
||||
qs.0011.param=inTCP
|
||||
qs.0011.type=port
|
||||
qs.0011.default=8887
|
||||
qs.0012.question=If you have a NAT or firewall, please be sure port ##inTCP## is open and can receive inbound connections!
|
||||
qs.0012.type=info_spliced
|
||||
qs.0015.question=<none>
|
||||
qs.0015.param=phttpRegister
|
||||
qs.0015.type=string>0
|
||||
qs.0015.default=http://i2p.net:8080/phttprelay/phttpRegister
|
||||
qs.0016.question=<none>
|
||||
qs.0016.param=phttpSend
|
||||
qs.0016.type=string>0
|
||||
qs.0016.default=http://i2p.net:8080/phttprelay/phttpSend
|
||||
qs.0017.question=<none>
|
||||
qs.0017.param=i2cpPort
|
||||
qs.0017.type=port
|
||||
qs.0017.default=7654
|
||||
qs.0018.question=
|
||||
qs.0019.question=40
|
||||
qs.0019.type=skip
|
||||
## leave some numbers free, for future additions
|
||||
|
||||
## bandwidth questions, skipped over
|
||||
qs.0030.question=Bandwidth limits
|
||||
qs.0030.type=category
|
||||
qs.0031.question=These bandwidth limits are fairly hard and unforgiving.\nWe do our best to not let any data beyond these limits be transferred at all, ever.\nSo keep that in mind, and set the limits to the upper bounds of what you can handle.\n\nPlease take note that these bandwidth limits are currently very, very strict, and the network itself does not consume\nmuch bandwidth. Everything will stop transferring data without warning if the limits are met - you really should set\nthis to -1 for now, at least until the new classed bandwidth limiter is implemented (currently planned for the 0.3.2 release)
|
||||
qs.0031.type=info
|
||||
qs.0032.question=
|
||||
qs.0033.question=Inbound bytes per second: (e.g. 16K, 16384, or -1 for unlimited)
|
||||
qs.0033.param=inBPS
|
||||
qs.0033.type=bandwidth
|
||||
qs.0033.default=-1
|
||||
qs.0034.question=Outbound bytes per second: (e.g. 16K, 16384, or -1 for unlimited)
|
||||
qs.0034.param=outBPS
|
||||
qs.0034.type=bandwidth
|
||||
qs.0034.default=-1
|
||||
qs.0035.question=
|
||||
qs.0036.question=45
|
||||
qs.0036.type=skip
|
||||
|
||||
qs.0040.question=<none>
|
||||
qs.0040.param=inBPS
|
||||
qs.0040.default=-1
|
||||
qs.0040.type=numeric
|
||||
qs.0041.question=<none>
|
||||
qs.0041.param=outBPS
|
||||
qs.0041.default=-1
|
||||
qs.0041.type=numeric
|
||||
qs.0042.question=45
|
||||
qs.0042.type=skip
|
||||
|
||||
qs.0045.question=<none>
|
||||
#qs.0045.question=Do you want very verbose debug logs when running the router by default (y/n)
|
||||
qs.0045.param=verboseLogs
|
||||
qs.0045.default=no
|
||||
qs.0045.type=boolean
|
||||
|
||||
qs.0050.question=End of configuration.
|
||||
|
||||
|
||||
libs.count=8
|
||||
libs.0001.name=i2p.jar
|
||||
libs.0001.islib=true
|
||||
libs.0002.name=i2ptunnel.jar
|
||||
libs.0002.islib=true
|
||||
libs.0003.name=router.jar
|
||||
libs.0003.islib=true
|
||||
libs.0004.name=fetchseeds.jar
|
||||
libs.0004.islib=true
|
||||
libs.0005.name=COPYING
|
||||
libs.0005.islib=false
|
||||
libs.0006.name=readme.txt
|
||||
libs.0006.islib=false
|
||||
libs.0007.name=hosts.txt
|
||||
libs.0007.islib=false
|
||||
libs.0008.name=mstreaming.jar
|
||||
libs.0008.islib=true
|
||||
|
66
installer/java/src/logger.config.template
Normal file
66
installer/java/src/logger.config.template
Normal file
@ -0,0 +1,66 @@
|
||||
# I2P Log Configuration File
|
||||
# Format:
|
||||
# d = date
|
||||
# c = class or log name
|
||||
# t = thread name
|
||||
# p = priority level of the log record
|
||||
# m = message
|
||||
logger.format=d p [t] c: m
|
||||
|
||||
# Date format:
|
||||
# uses the java.text.SimpleDateFormat
|
||||
logger.dateFormat=HH:mm:ss.SSS
|
||||
|
||||
# Log file name:
|
||||
# This is the log file name before being rotated
|
||||
# '#' is replaced with the current log number for that day
|
||||
# If # is not specified, logs are not rotated
|
||||
logger.logFileName=log-#.txt
|
||||
|
||||
# Log file size:
|
||||
# Maximum size of each log file:
|
||||
# 32g <-- 32 gigabytes (32*1024*1024*1024 bytes)
|
||||
# 10m <-- 10 megabytes (10*1024*1024 bytes)
|
||||
# 66k <-- 66 kilobytes (66*1024 bytes)
|
||||
# 42 <-- 42 bytes
|
||||
logger.logFileSize=3m
|
||||
|
||||
# Log rotation limit:
|
||||
# Maximum number of logs to keep per day - lower log numbers
|
||||
# are reused once the limit is reached. Ignored if there is no
|
||||
# '#' in the logFileName.
|
||||
logger.logRotationLimit=3
|
||||
|
||||
# Display on screen:
|
||||
# In addition to the logging, send filtered log messages to the
|
||||
# standard output
|
||||
# true or false
|
||||
logger.displayOnScreen=true
|
||||
|
||||
# Default level:
|
||||
# Define the minimum log level to be displayed unless
|
||||
# specified otherwise.
|
||||
#
|
||||
# Log levels, from least severe to most, are:
|
||||
# DEBUG : verbose debugging info
|
||||
# INFO : component status messages
|
||||
# WARN : bad situation but recoverable
|
||||
# ERROR : component error
|
||||
# CRIT : your hard drive is on fire
|
||||
#
|
||||
# Less severe levels always include more severe ones (e.g.
|
||||
# if you're listening to debug messages, you'll get info messages
|
||||
# too)
|
||||
logger.defaultLevel=##_logger_level##
|
||||
|
||||
# Minimum log level for a record to be displayed on the screen
|
||||
# This check occurs after other filters occur, and only affects what is
|
||||
# show on console, and only does anything if displayOnScreen=true
|
||||
logger.minimumOnScreenLevel=DEBUG
|
||||
|
||||
# Records:
|
||||
# Override the defaultLevel for all classes under the given package
|
||||
# or class
|
||||
logger.record.net.i2p=##_logger_level2##
|
||||
##_logger_notverbose##
|
||||
|
3
installer/java/src/reseed.bat.template
Normal file
3
installer/java/src/reseed.bat.template
Normal file
@ -0,0 +1,3 @@
|
||||
cd ##_scripts_installdir##
|
||||
java -jar lib\fetchseeds.jar netDb
|
||||
pause
|
4
installer/java/src/reseed.sh.template
Normal file
4
installer/java/src/reseed.sh.template
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
cd ##_scripts_installdir##
|
||||
java -jar lib/fetchseeds.jar netDb
|
||||
echo Router network database reseeded
|
152
installer/java/src/router.config.template
Normal file
152
installer/java/src/router.config.template
Normal file
@ -0,0 +1,152 @@
|
||||
# I2P router configuration
|
||||
# Created on ##NOW##
|
||||
|
||||
# TCP configuration, for inbound TCP/IP connections
|
||||
##_router_hn##
|
||||
##_router_port##
|
||||
##_router_lavalid##
|
||||
# unless you really really know what you're doing, keep listenAddressIsValid=false
|
||||
##_router_tcpdisable##
|
||||
|
||||
# maximum number of TCP connections we will want to
|
||||
# attempt to establish at once (each of which
|
||||
# requires a 2048bit DH exchange)
|
||||
i2np.tcp.concurrentEstablishers=5
|
||||
|
||||
# Polling HTTP configuration, which is used to keep your router's clock in sync
|
||||
# [also for communication when no inbound connections are possible, once its fixed up again]
|
||||
##_router_phttpreg##
|
||||
##_router_phttpsend##
|
||||
|
||||
# The following option specifies whether the router wants to keep the router's internal time in sync
|
||||
# with the PHTTP relay's clock (which should be NTP synced). If however you are sure your local machine
|
||||
# always has the correct time, you can set this to false (but your clock MUST be synced - see
|
||||
# http://wiki.invisiblenet.net/iip-wiki?I2PTiming for more info.
|
||||
i2np.phttp.trustRelayTime=true
|
||||
|
||||
# I2CP client port, for client connections
|
||||
i2cp.port=##_router_i2cp_port##
|
||||
|
||||
# I2P router administrative web port (currently only responds to /routerConsole.html)
|
||||
router.adminPort=7655
|
||||
# Bandwidth limits
|
||||
# These limits are for all i2np connections - tcp or whatever
|
||||
# They are hard enforced with no smoothing.
|
||||
# XXX Until the 0.3.2 release, these should NOT BE USED. Their values will be ignored!!!
|
||||
i2np.bandwidth.inboundBytesPerMinute=##_router_inbps##
|
||||
i2np.bandwidth.outboundBytesPerMinute=##_router_outbps##
|
||||
|
||||
# Publish peer rankings
|
||||
# If true, include the current liveliness and reliability rankings in one's published RouterInfo data
|
||||
# Setting this to true will help debug the network and is especially useful while we'return still testing
|
||||
# However, traffic analysis may be easier with this data published (though there's no reason to think people
|
||||
# can't just fake the info in this).
|
||||
# Since we're still very much < 1.0, this will be true for the current release by default. As we get some
|
||||
# network helth information and tune the ranking algorithms, this will become false by default.
|
||||
# You, of course, can change this to either true or false whenever you'd like. This is only read
|
||||
# on router startup though, so you need to restart the router if you change it.
|
||||
router.publishPeerRankings=true
|
||||
|
||||
# Keep message history
|
||||
# This series of options can help out in debugging the network by keeping a
|
||||
# seperate log of all messages sent over the network (but without any personally identifiable information)
|
||||
# This is entirely optional, but would be greatly appreciated during the
|
||||
# development phase of the network since it would allow the developers to detect
|
||||
# errors much more easily
|
||||
router.keepHistory=false
|
||||
|
||||
# Submit message history
|
||||
# This option works only if router.keepHistory is true and periodically sends
|
||||
# in the router history logs to the developers (specifically, it submits the file
|
||||
# via HTTP POST to http://i2p.net/cgi-bin/submitMessageHistory - you can see a sample of what
|
||||
# one of those files looks like at http://i2p.net/~jrandom/sampleHist.txt)
|
||||
# After submitting this file, it deletes the local copy (otherwise the file will grow
|
||||
# without bound - tens of MB per day)
|
||||
# Again, this is entirely optional, but would be greatly appreciated as it should help
|
||||
# out the development process
|
||||
router.submitHistory=false
|
||||
|
||||
# If your router is really slow, you'll need to update the following job parameters
|
||||
|
||||
# limit the maximum number of concurrent operations
|
||||
router.maxJobRunners=1
|
||||
|
||||
# if a job waits more than this amount of time (in
|
||||
# milliseconds) before running, spit out a warning
|
||||
router.jobLagWarning=8000
|
||||
|
||||
# if a job waits more than this amount of time (in
|
||||
# milliseconds) before running, kill the router
|
||||
router.jobLagFatal=30000
|
||||
|
||||
# if a job takes more than this amount of time (in
|
||||
# milliseconds) to run, spit out a warning
|
||||
router.jobRunWarning=8000
|
||||
|
||||
# if a job takes more than this amount of time (in
|
||||
# milliseconds) to run, kill the router
|
||||
router.jobRunFatal=30000
|
||||
|
||||
# wait until the router has been up for this long
|
||||
# (in milliseconds) before honoring any fatalities
|
||||
# since during startup, jobs are run sequentially
|
||||
# and CPU intensive tasks are needed
|
||||
router.jobWarmupTime=600000
|
||||
|
||||
# Target clients
|
||||
# How many concurrent clients the router should prepare for
|
||||
# This, factored in with the tunnel settings, determines the size of the pools -
|
||||
# too many, and your machine consumes excessive CPU and bandwidth, too few and your
|
||||
# clients take too long to startup.
|
||||
# e.g. If you are running an eepsite, an eepProxy, an irc proxy, and a squid proxy, set this to 4
|
||||
router.targetClients=2
|
||||
|
||||
# Number of inbound tunnels per client
|
||||
# This determines how many inbound tunnels will be allocated per client at a time.
|
||||
# This is a key factor in the reliability of a client receiving messages
|
||||
# As above, too many and your machine gets hosed, too few and the pool is slow.
|
||||
# 2 should be sufficient - prior to 0.2.5, we have all had only 1
|
||||
tunnels.numInbound=2
|
||||
|
||||
# Number of outbound tunnels per client
|
||||
# This determines how many outbound tunnels must exist when a client is in operation.
|
||||
# XXX Not currently enforced - ignore this setting
|
||||
tunnels.numOutbound=2
|
||||
|
||||
|
||||
# Depth of inbound tunnels
|
||||
# This determines the length of inbound tunnels created - how many remote routers to
|
||||
# include (0 means no remote routers, 3 means a total of four routers, including
|
||||
# the local one, etc). This is a key factor in the reliability and anonymity
|
||||
# provided by I2P
|
||||
# Users should simply leave this as 2 for now, at least until the tunnels are more reliable (post 0.3)
|
||||
tunnels.depthInbound=2
|
||||
|
||||
# Depth of outbound tunnels
|
||||
# This determines the length of outbound tunnels created - how many remote routers to
|
||||
# include (0 means no remote routers, 3 means a total of four routers, including
|
||||
# the local one, etc). This is a key factor in the reliability and anonymity
|
||||
# provided by I2P
|
||||
# Users should simply leave this as 2 for now, at least until the tunnels are more reliable (post 0.3)
|
||||
tunnels.depthOutbound=2
|
||||
|
||||
# Tunnel duration
|
||||
# This determines how long tunnels we create should last for (in milliseconds). Too
|
||||
# long and they are more prone to failure, too short and people need to do more network
|
||||
# database lookups. The default of 10 minutes (600000 ms) should be used
|
||||
# You should not change this setting unless you really know what you're doing
|
||||
tunnels.tunnelDuration=600000
|
||||
|
||||
# Max waiting jobs
|
||||
# If your router is getting heavily overloaded (due to slow CPU or excess network
|
||||
# activity), your router's performance will seriously degrade, increasing its
|
||||
# load further and delaying any messages sent through your router. The max waiting
|
||||
# jobs configuration parameter is a throttle, saying that if there are more than
|
||||
# that many 'jobs' that want to run ASAP at any given time, additional jobs may
|
||||
# be summarily dropped. That will reduce your load and cause others to reduce
|
||||
# their dependence on you (further reducing your load). The default value of 20
|
||||
# should be sufficient, but may be increased if desired. Less than 20 is not
|
||||
# recommended, as certain normal events can queue up 10 or so jobs at a time
|
||||
# (all of which only take a few milliseconds). Leave this alone unless you know
|
||||
# what you're doing
|
||||
router.maxWaitingJobs=20
|
5
installer/java/src/startFoo.bat.template
Normal file
5
installer/java/src/startFoo.bat.template
Normal file
@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
title ##_scripts_winttl##
|
||||
cd ##_scripts_installdir##
|
||||
echo ##_scripts_message##
|
||||
java -DloggerFilenameOverride=logs\##_scripts_logname## -Djava.library.path=. -Dcrypto.dh.precalc.min=0 -cp lib\mstreaming.jar;lib\i2p.jar -jar lib\i2ptunnel.jar -nocli -e "config localhost ##_scripts_port##" -e "##_scripts_cmd##"
|
6
installer/java/src/startFoo.sh.template
Normal file
6
installer/java/src/startFoo.sh.template
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
cd ##_scripts_installdir##
|
||||
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
|
||||
nohup nice java -DloggerFilenameOverride=logs/##_scripts_logname## -Djava.library.path=. -Dcrypto.dh.precalc.min=0 -cp lib/mstreaming.jar:lib/i2p.jar -jar lib/i2ptunnel.jar -nocli -e "config localhost ##_scripts_port##" -e "##_scripts_cmd##" > /dev/null &
|
||||
|
||||
echo '##_scripts_message##'
|
9
installer/java/src/startRouter.bat.template
Normal file
9
installer/java/src/startRouter.bat.template
Normal file
@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
title I2P Router
|
||||
cd ##_scripts_installdir##
|
||||
|
||||
REM the -XX args are workarounds for bugs in java 1.4.2's garbage collector
|
||||
REM replace java with javaw if you don't want a window to pop up
|
||||
|
||||
java -cp lib\i2p.jar;lib\router.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
|
||||
pause
|
8
installer/java/src/startRouter.sh.template
Normal file
8
installer/java/src/startRouter.sh.template
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
cd ##_scripts_installdir##
|
||||
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
|
||||
# the -XX args are workarounds for bugs in java 1.4.2's garbage collector
|
||||
nohup nice java -cp lib/i2p.jar:lib/router.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
|
||||
# Save the pid just in case we ever want to stop the router
|
||||
echo $! > router.pid
|
||||
echo I2P Router started
|
3
installer/java/src/stopRouter.sh.template
Normal file
3
installer/java/src/stopRouter.sh.template
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd ##_scripts_installdir##
|
||||
kill `cat router.pid`
|
88
readme.txt
Normal file
88
readme.txt
Normal file
@ -0,0 +1,88 @@
|
||||
$Id: readme.txt,v 1.5 2004/03/21 20:03:54 jrandom Exp $
|
||||
I2P Router 0.3
|
||||
|
||||
You have installed a development release of I2P - a variable latency secure
|
||||
and anonymous communication network. I2P is NOT a filesharing network, or an
|
||||
email network, or a publishing network - it is simply an anonymous communication
|
||||
layer - kind of an anonymous IP. This installation includes an application
|
||||
called "I2PTunnel", which allows normal TCP/IP applications to run over I2P,
|
||||
offering functionality such as access to anonymous irc servers, anonymous
|
||||
websites ("eepsites"), etc.
|
||||
|
||||
Since this is a *development* release, I2P should not be depended upon for
|
||||
anonymity or security. The network is very small, and there are certainly bugs
|
||||
and suboptimal features. Participating in the network at this time should be
|
||||
for exploration and to evaluate I2P's functionality and suitability for your
|
||||
later needs. Once I2P 1.0 is out, wider adoption may be appropriate, but until
|
||||
that time, we do want to keep the I2P community small, since you are all part of
|
||||
the development team.
|
||||
|
||||
=== How to get started
|
||||
|
||||
Like a TCP/IP stack, installing the I2P "router" itself doesn't really do much.
|
||||
You can fire it up with the script startRouter.sh (or startRouter.bat on
|
||||
windows), and its management console can be seen via http://localhost:7655/
|
||||
|
||||
Once your router has started up, it may take a few minutes to get integrated
|
||||
with the network (you'll see a few TCP connections listed on the management
|
||||
console). At that point, you can start up any of the various proxies:
|
||||
* startEepProxy: starts an HTTP proxy to access eepsites. Set your browser's
|
||||
HTTP proxy to localhost:4444 and you can browse various anonymously hosted
|
||||
sites, ala http://duck.i2p/. In addition, the default proxy is set up to
|
||||
tunnel any HTTP requests that don't point at an eepsite (e.g. http://i2p.net/)
|
||||
through I2P to an outbound squid proxy - with this, you can browse the web
|
||||
anonymously.
|
||||
* startIrcProxy: starts an anonymous tunnel to an anonymously hosted IRC server
|
||||
(irc.duck.i2p) - use your favorite irc client and connect to localhost:6668
|
||||
and join us on #i2p
|
||||
|
||||
=== Problems accessing eepsites or servers
|
||||
|
||||
I2P is not a distributed data store (ala freenet / mnet / etc) - sites are only
|
||||
reachable when the host serving that data is up (and their router is running).
|
||||
If you persistently can't reach the irc server, the squid proxy, or some common
|
||||
eepsites, check your management console (http://localhost:7655/) and make sure
|
||||
you have TCP connections. If you don't have any, make sure your firewall / NAT
|
||||
allows inbound access to your I2P port (which you specified during
|
||||
installation). If thats fine, but you only see one routerInfo-*.dat file
|
||||
underneath your netDb/ directory, run the reseed script to pull some new peer
|
||||
references (it contacts http://i2p.net/i2pdb/ and downloads those files.
|
||||
alternately, you can get routerInfo files from any of your friends, etc)
|
||||
|
||||
If you still have problems, get in touch with the I2P team (contact info below)
|
||||
|
||||
=== Resources / contact info
|
||||
|
||||
I2P is currently revamping our website, so the two main resources are
|
||||
http://i2p.net/ and http://wiki.invisiblenet.net/iip-wiki?I2P (our website's
|
||||
work-in-progress location is http://drupal.i2p.net/ for those who want to see
|
||||
whats coming down the pipe). The development and user community hangs out on
|
||||
a few different linked irc chats - IIP's #i2p, freenode.net's #i2p, and the
|
||||
in-I2P irc network's #i2p (both irc.baffled.i2p and irc.duck.i2p). All of those
|
||||
channels are hooked together, so join whichever one meets your needs.
|
||||
|
||||
There is also a relatively low traffic mailing list:
|
||||
http://i2p.net/mailman/listinfo/i2p with archives at
|
||||
http://i2p.net/pipermail/i2p/
|
||||
|
||||
The source can be retrieved from http://i2p.net/i2p/ as well as the latest
|
||||
binary distributions.
|
||||
|
||||
You can pull the latest code via cvs:
|
||||
cvs -d :pserver:anoncvs@i2p.net:/cvsroot login
|
||||
cvs -d :pserver:anoncvs@i2p.net:/cvsroot co i2p
|
||||
The password is "anoncvs".
|
||||
|
||||
=== Acknowledgements
|
||||
|
||||
We are a small group of volunteers spread around several continents, working to
|
||||
advance different aspects of the project and discussing the design of the
|
||||
network. For a current list of team members, please see
|
||||
http://drupal.i2p.net/team
|
||||
|
||||
=== Licenses
|
||||
|
||||
All code included here is released under an open source license. To review
|
||||
the I2P license policy, please see http://drupal.i2p.net/node/view/66.
|
||||
If there is any confusion, please see the source code or contact the
|
||||
developers on the i2p list.
|
9
router/doc/readme.license.txt
Normal file
9
router/doc/readme.license.txt
Normal file
@ -0,0 +1,9 @@
|
||||
$Id$
|
||||
|
||||
the i2p/router/ module is the root of the I2P
|
||||
router, and everything within it is released
|
||||
according to the terms of the I2P license policy.
|
||||
That means everything contained within the
|
||||
i2p/router module is released into the public
|
||||
domain unless otherwise marked. Alternate licenses
|
||||
that may be used include BSD, Cryptix, and MIT.
|
35
router/java/build.xml
Normal file
35
router/java/build.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2p_router">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src:./test" debug="true" destdir="./build/obj" classpath="../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/router.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:./test:../../core/java/src:../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2P Router" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
84
router/java/src/net/i2p/data/i2np/DataMessage.java
Normal file
84
router/java/src/net/i2p/data/i2np/DataMessage.java
Normal file
@ -0,0 +1,84 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines a message containing arbitrary bytes of data
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DataMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DataMessage.class);
|
||||
public final static int MESSAGE_TYPE = 20;
|
||||
private byte _data[];
|
||||
|
||||
public DataMessage() {
|
||||
_data = null;
|
||||
}
|
||||
|
||||
public byte[] getData() { return _data; }
|
||||
public void setData(byte data[]) { _data = data; }
|
||||
|
||||
public int getSize() { return _data.length; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
int size = (int)DataHelper.readLong(in, 4);
|
||||
_data = new byte[size];
|
||||
int read = read(in, _data);
|
||||
if (read != size)
|
||||
throw new DataFormatException("Not enough bytes to read (read = " + read + ", expected = " + size + ")");
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream((_data != null ? _data.length + 4 : 4));
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, (_data != null ? _data.length : 0));
|
||||
os.write(_data);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getData());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DataMessage) ) {
|
||||
DataMessage msg = (DataMessage)object;
|
||||
return DataHelper.eq(getData(),msg.getData());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DataMessage: ");
|
||||
buf.append("\n\tData: ").append(DataHelper.toString(getData(), 64));
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router to help integrate into
|
||||
* the network by searching for routers in a particular keyspace.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseFindNearestMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseFindNearestMessage.class);
|
||||
public final static int MESSAGE_TYPE = 4;
|
||||
private Hash _key;
|
||||
private Hash _from;
|
||||
|
||||
public DatabaseFindNearestMessage() {
|
||||
setSearchKey(null);
|
||||
setFromHash(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key being searched for
|
||||
*/
|
||||
public Hash getSearchKey() { return _key; }
|
||||
public void setSearchKey(Hash key) { _key = key; }
|
||||
|
||||
/**
|
||||
* Contains the SHA256 Hash of the RouterIdentity sending the message
|
||||
*/
|
||||
public Hash getFromHash() { return _from; }
|
||||
public void setFromHash(Hash from) { _from = from; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
_from = new Hash();
|
||||
_from.readBytes(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_key == null) || (_from == null) ) throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
_from.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getSearchKey()) +
|
||||
DataHelper.hashCode(getFromHash());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseFindNearestMessage) ) {
|
||||
DatabaseFindNearestMessage msg = (DatabaseFindNearestMessage)object;
|
||||
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
|
||||
DataHelper.eq(getFromHash(),msg.getFromHash());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseFindNearestMessage: ");
|
||||
buf.append("\n\tSearch Key: ").append(getSearchKey());
|
||||
buf.append("\n\tFrom: ").append(getFromHash());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
165
router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
Normal file
165
router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
Normal file
@ -0,0 +1,165 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router to search for a
|
||||
* key in the network database.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseLookupMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseLookupMessage.class);
|
||||
public final static int MESSAGE_TYPE = 2;
|
||||
private Hash _key;
|
||||
private RouterInfo _from;
|
||||
private TunnelId _replyTunnel;
|
||||
private Set _dontIncludePeers;
|
||||
|
||||
public DatabaseLookupMessage() {
|
||||
setSearchKey(null);
|
||||
setFrom(null);
|
||||
setDontIncludePeers(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key being searched for
|
||||
*/
|
||||
public Hash getSearchKey() { return _key; }
|
||||
public void setSearchKey(Hash key) { _key = key; }
|
||||
|
||||
/**
|
||||
* Contains the current router info of the router who requested this lookup
|
||||
*
|
||||
*/
|
||||
public RouterInfo getFrom() { return _from; }
|
||||
public void setFrom(RouterInfo from) { _from = from; }
|
||||
|
||||
/**
|
||||
* Contains the tunnel ID a reply should be sent to
|
||||
*
|
||||
*/
|
||||
public TunnelId getReplyTunnel() { return _replyTunnel; }
|
||||
public void setReplyTunnel(TunnelId replyTunnel) { _replyTunnel = replyTunnel; }
|
||||
|
||||
/**
|
||||
* Set of peers that a lookup reply should NOT include
|
||||
*
|
||||
* @return Set of Hash objects, each of which is the H(routerIdentity) to skip
|
||||
*/
|
||||
public Set getDontIncludePeers() { return _dontIncludePeers; }
|
||||
public void setDontIncludePeers(Set peers) {
|
||||
if (peers != null)
|
||||
_dontIncludePeers = new HashSet(peers);
|
||||
else
|
||||
_dontIncludePeers = null;
|
||||
}
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
_from = new RouterInfo();
|
||||
_from.readBytes(in);
|
||||
boolean tunnelSpecified = DataHelper.readBoolean(in).booleanValue();
|
||||
if (tunnelSpecified) {
|
||||
_replyTunnel = new TunnelId();
|
||||
_replyTunnel.readBytes(in);
|
||||
}
|
||||
int numPeers = (int)DataHelper.readLong(in, 2);
|
||||
if ( (numPeers < 0) || (numPeers >= (1<<16) ) )
|
||||
throw new DataFormatException("Invalid number of peers - " + numPeers);
|
||||
Set peers = new HashSet(numPeers);
|
||||
for (int i = 0; i < numPeers; i++) {
|
||||
Hash peer = new Hash();
|
||||
peer.readBytes(in);
|
||||
peers.add(peer);
|
||||
}
|
||||
_dontIncludePeers = peers;
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if (_key == null) throw new I2NPMessageException("Key being searched for not specified");
|
||||
if (_from == null) throw new I2NPMessageException("From address not specified");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
_from.writeBytes(os);
|
||||
if (_replyTunnel != null) {
|
||||
DataHelper.writeBoolean(os, Boolean.TRUE);
|
||||
_replyTunnel.writeBytes(os);
|
||||
} else {
|
||||
DataHelper.writeBoolean(os, Boolean.FALSE);
|
||||
}
|
||||
if ( (_dontIncludePeers == null) || (_dontIncludePeers.size() <= 0) ) {
|
||||
DataHelper.writeLong(os, 2, 0);
|
||||
} else {
|
||||
DataHelper.writeLong(os, 2, _dontIncludePeers.size());
|
||||
for (Iterator iter = _dontIncludePeers.iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
peer.writeBytes(os);
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getSearchKey()) +
|
||||
DataHelper.hashCode(getFrom()) +
|
||||
DataHelper.hashCode(getReplyTunnel()) +
|
||||
DataHelper.hashCode(_dontIncludePeers);
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseLookupMessage) ) {
|
||||
DatabaseLookupMessage msg = (DatabaseLookupMessage)object;
|
||||
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
|
||||
DataHelper.eq(getFrom(),msg.getFrom()) &&
|
||||
DataHelper.eq(getReplyTunnel(),msg.getReplyTunnel()) &&
|
||||
DataHelper.eq(_dontIncludePeers,msg.getDontIncludePeers());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseLookupMessage: ");
|
||||
buf.append("\n\tSearch Key: ").append(getSearchKey());
|
||||
buf.append("\n\tFrom: ").append(getFrom());
|
||||
buf.append("\n\tReply Tunnel: ").append(getReplyTunnel());
|
||||
buf.append("\n\tDont Include Peers: ").append(getDontIncludePeers());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router in response to a
|
||||
* search (DatabaseFindNearest or DatabaseLookup) when it doesn't have the value,
|
||||
* specifying what routers it would search.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseSearchReplyMessage.class);
|
||||
public final static int MESSAGE_TYPE = 3;
|
||||
private Hash _key;
|
||||
private List _routerInfoStructures;
|
||||
private Hash _from;
|
||||
|
||||
public DatabaseSearchReplyMessage() {
|
||||
setSearchKey(null);
|
||||
_routerInfoStructures = new ArrayList();
|
||||
setFromHash(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key being searched for
|
||||
*/
|
||||
public Hash getSearchKey() { return _key; }
|
||||
public void setSearchKey(Hash key) { _key = key; }
|
||||
|
||||
public int getNumReplies() { return _routerInfoStructures.size(); }
|
||||
public RouterInfo getReply(int index) { return (RouterInfo)_routerInfoStructures.get(index); }
|
||||
public void addReply(RouterInfo info) { _routerInfoStructures.add(info); }
|
||||
public void addReplies(Collection replies) { _routerInfoStructures.addAll(replies); }
|
||||
|
||||
public Hash getFromHash() { return _from; }
|
||||
public void setFromHash(Hash from) { _from = from; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
|
||||
int compressedLength = (int)DataHelper.readLong(in, 2);
|
||||
byte compressedData[] = new byte[compressedLength];
|
||||
int read = DataHelper.read(in, compressedData);
|
||||
if (read != compressedLength)
|
||||
throw new IOException("Not enough data to decompress");
|
||||
byte decompressedData[] = DataHelper.decompress(compressedData);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(decompressedData);
|
||||
int num = (int)DataHelper.readLong(bais, 1);
|
||||
_routerInfoStructures.clear();
|
||||
for (int i = 0; i < num; i++) {
|
||||
RouterInfo info = new RouterInfo();
|
||||
info.readBytes(bais);
|
||||
addReply(info);
|
||||
}
|
||||
|
||||
_from = new Hash();
|
||||
_from.readBytes(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if (_key == null)
|
||||
throw new I2NPMessageException("Key in reply to not specified");
|
||||
if (_routerInfoStructures == null)
|
||||
throw new I2NPMessageException("RouterInfo replies are null");
|
||||
if (_routerInfoStructures.size() <= 0)
|
||||
throw new I2NPMessageException("No replies specified in SearchReply! Always include oneself!");
|
||||
if (_from == null)
|
||||
throw new I2NPMessageException("No 'from' address specified!");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
DataHelper.writeLong(baos, 1, _routerInfoStructures.size());
|
||||
for (int i = 0; i < getNumReplies(); i++) {
|
||||
RouterInfo info = getReply(i);
|
||||
info.writeBytes(baos);
|
||||
}
|
||||
|
||||
byte compressed[] = DataHelper.compress(baos.toByteArray());
|
||||
DataHelper.writeLong(os, 2, compressed.length);
|
||||
os.write(compressed);
|
||||
_from.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseSearchReplyMessage) ) {
|
||||
DatabaseSearchReplyMessage msg = (DatabaseSearchReplyMessage)object;
|
||||
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
|
||||
DataHelper.eq(getFromHash(),msg.getFromHash()) &&
|
||||
DataHelper.eq(_routerInfoStructures,msg._routerInfoStructures);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getSearchKey()) +
|
||||
DataHelper.hashCode(getFromHash()) +
|
||||
DataHelper.hashCode(_routerInfoStructures);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseSearchReplyMessage: ");
|
||||
buf.append("\n\tSearch Key: ").append(getSearchKey());
|
||||
buf.append("\n\tReplies: # = ").append(getNumReplies());
|
||||
for (int i = 0; i < getNumReplies(); i++) {
|
||||
buf.append("\n\t\tReply [").append(i).append("]: ").append(getReply(i));
|
||||
}
|
||||
buf.append("\n\tFrom: ").append(getFromHash());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
170
router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
Normal file
170
router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
Normal file
@ -0,0 +1,170 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router to test the network
|
||||
* database reachability, as well as the reply message sent back.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseStoreMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseStoreMessage.class);
|
||||
public final static int MESSAGE_TYPE = 1;
|
||||
private Hash _key;
|
||||
private int _type;
|
||||
private LeaseSet _leaseSet;
|
||||
private RouterInfo _info;
|
||||
|
||||
public final static int KEY_TYPE_ROUTERINFO = 0;
|
||||
public final static int KEY_TYPE_LEASESET = 1;
|
||||
|
||||
public DatabaseStoreMessage() {
|
||||
setValueType(-1);
|
||||
setKey(null);
|
||||
setLeaseSet(null);
|
||||
setRouterInfo(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key in the network database being stored
|
||||
*
|
||||
*/
|
||||
public Hash getKey() { return _key; }
|
||||
public void setKey(Hash key) { _key = key; }
|
||||
|
||||
/**
|
||||
* Defines the router info value in the network database being stored
|
||||
*
|
||||
*/
|
||||
public RouterInfo getRouterInfo() { return _info; }
|
||||
public void setRouterInfo(RouterInfo routerInfo) {
|
||||
_info = routerInfo;
|
||||
if (_info != null)
|
||||
setValueType(KEY_TYPE_ROUTERINFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the lease set value in the network database being stored
|
||||
*
|
||||
*/
|
||||
public LeaseSet getLeaseSet() { return _leaseSet; }
|
||||
public void setLeaseSet(LeaseSet leaseSet) {
|
||||
_leaseSet = leaseSet;
|
||||
if (_leaseSet != null)
|
||||
setValueType(KEY_TYPE_LEASESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines type of key being stored in the network database -
|
||||
* either KEY_TYPE_ROUTERINFO or KEY_TYPE_LEASESET
|
||||
*
|
||||
*/
|
||||
public int getValueType() { return _type; }
|
||||
public void setValueType(int type) { _type = type; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
_log.debug("Hash read: " + _key.toBase64());
|
||||
_type = (int)DataHelper.readLong(in, 1);
|
||||
if (_type == KEY_TYPE_LEASESET) {
|
||||
_leaseSet = new LeaseSet();
|
||||
_leaseSet.readBytes(in);
|
||||
} else if (_type == KEY_TYPE_ROUTERINFO) {
|
||||
_info = new RouterInfo();
|
||||
int compressedSize = (int)DataHelper.readLong(in, 2);
|
||||
byte compressed[] = new byte[compressedSize];
|
||||
int read = DataHelper.read(in, compressed);
|
||||
if (read != compressedSize)
|
||||
throw new I2NPMessageException("Invalid compressed data size");
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(DataHelper.decompress(compressed));
|
||||
_info.readBytes(bais);
|
||||
} else {
|
||||
throw new I2NPMessageException("Invalid type of key read from the structure - " + _type);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if (_key == null) throw new I2NPMessageException("Invalid key");
|
||||
if ( (_type != KEY_TYPE_LEASESET) && (_type != KEY_TYPE_ROUTERINFO) ) throw new I2NPMessageException("Invalid key type");
|
||||
if ( (_type == KEY_TYPE_LEASESET) && (_leaseSet == null) ) throw new I2NPMessageException("Missing lease set");
|
||||
if ( (_type == KEY_TYPE_ROUTERINFO) && (_info == null) ) throw new I2NPMessageException("Missing router info");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(256);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
DataHelper.writeLong(os, 1, _type);
|
||||
if (_type == KEY_TYPE_LEASESET) {
|
||||
_leaseSet.writeBytes(os);
|
||||
} else if (_type == KEY_TYPE_ROUTERINFO) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
|
||||
_info.writeBytes(baos);
|
||||
byte uncompressed[] = baos.toByteArray();
|
||||
byte compressed[] = DataHelper.compress(uncompressed);
|
||||
DataHelper.writeLong(os, 2, compressed.length);
|
||||
os.write(compressed);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getKey()) +
|
||||
DataHelper.hashCode(getLeaseSet()) +
|
||||
DataHelper.hashCode(getRouterInfo()) +
|
||||
getValueType();
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseStoreMessage) ) {
|
||||
DatabaseStoreMessage msg = (DatabaseStoreMessage)object;
|
||||
return DataHelper.eq(getKey(),msg.getKey()) &&
|
||||
DataHelper.eq(getLeaseSet(),msg.getLeaseSet()) &&
|
||||
DataHelper.eq(getRouterInfo(),msg.getRouterInfo()) &&
|
||||
DataHelper.eq(getValueType(),msg.getValueType());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseStoreMessage: ");
|
||||
buf.append("\n\tExpiration: ").append(getMessageExpiration());
|
||||
buf.append("\n\tUnique ID: ").append(getUniqueId());
|
||||
buf.append("\n\tKey: ").append(getKey());
|
||||
buf.append("\n\tValue Type: ").append(getValueType());
|
||||
buf.append("\n\tRouter Info: ").append(getRouterInfo());
|
||||
buf.append("\n\tLease Set: ").append(getLeaseSet());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
274
router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
Normal file
274
router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
Normal file
@ -0,0 +1,274 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
|
||||
/**
|
||||
* Contains the delivery instructions
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DeliveryInstructions extends DataStructureImpl {
|
||||
private final static Log _log = new Log(DeliveryInstructions.class);
|
||||
private boolean _encrypted;
|
||||
private SessionKey _encryptionKey;
|
||||
private int _deliveryMode;
|
||||
public final static int DELIVERY_MODE_LOCAL = 0;
|
||||
public final static int DELIVERY_MODE_DESTINATION = 1;
|
||||
public final static int DELIVERY_MODE_ROUTER = 2;
|
||||
public final static int DELIVERY_MODE_TUNNEL = 3;
|
||||
private Hash _destinationHash;
|
||||
private Hash _routerHash;
|
||||
private TunnelId _tunnelId;
|
||||
private boolean _delayRequested;
|
||||
private long _delaySeconds;
|
||||
|
||||
private final static int FLAG_MODE_LOCAL = 0;
|
||||
private final static int FLAG_MODE_DESTINATION = 1;
|
||||
private final static int FLAG_MODE_ROUTER = 2;
|
||||
private final static int FLAG_MODE_TUNNEL = 3;
|
||||
|
||||
private final static long FLAG_ENCRYPTED = 128;
|
||||
private final static long FLAG_MODE = 96;
|
||||
private final static long FLAG_DELAY = 16;
|
||||
|
||||
public DeliveryInstructions() {
|
||||
setEncrypted(false);
|
||||
setEncryptionKey(null);
|
||||
setDeliveryMode(-1);
|
||||
setDestination(null);
|
||||
setRouter(null);
|
||||
setTunnelId(null);
|
||||
setDelayRequested(false);
|
||||
setDelaySeconds(0);
|
||||
}
|
||||
|
||||
public boolean getEncrypted() { return _encrypted; }
|
||||
public void setEncrypted(boolean encrypted) { _encrypted = encrypted; }
|
||||
public SessionKey getEncryptionKey() { return _encryptionKey; }
|
||||
public void setEncryptionKey(SessionKey key) { _encryptionKey = key; }
|
||||
public int getDeliveryMode() { return _deliveryMode; }
|
||||
public void setDeliveryMode(int mode) { _deliveryMode = mode; }
|
||||
public Hash getDestination() { return _destinationHash; }
|
||||
public void setDestination(Hash dest) { _destinationHash = dest; }
|
||||
public Hash getRouter() { return _routerHash; }
|
||||
public void setRouter(Hash router) { _routerHash = router; }
|
||||
public TunnelId getTunnelId() { return _tunnelId; }
|
||||
public void setTunnelId(TunnelId id) { _tunnelId = id; }
|
||||
public boolean getDelayRequested() { return _delayRequested; }
|
||||
public void setDelayRequested(boolean req) { _delayRequested = req; }
|
||||
public long getDelaySeconds() { return _delaySeconds; }
|
||||
public void setDelaySeconds(long seconds) { _delaySeconds = seconds; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
long flags = DataHelper.readLong(in, 1);
|
||||
_log.debug("Read flags: " + flags + " mode: " + flagMode(flags));
|
||||
|
||||
if (flagEncrypted(flags)) {
|
||||
SessionKey k = new SessionKey();
|
||||
k.readBytes(in);
|
||||
setEncryptionKey(k);
|
||||
setEncrypted(true);
|
||||
} else {
|
||||
setEncrypted(false);
|
||||
}
|
||||
|
||||
setDeliveryMode(flagMode(flags));
|
||||
switch (flagMode(flags)) {
|
||||
case FLAG_MODE_LOCAL:
|
||||
break;
|
||||
case FLAG_MODE_DESTINATION:
|
||||
Hash destHash = new Hash();
|
||||
destHash.readBytes(in);
|
||||
setDestination(destHash);
|
||||
break;
|
||||
case FLAG_MODE_ROUTER:
|
||||
Hash routerHash = new Hash();
|
||||
routerHash.readBytes(in);
|
||||
setRouter(routerHash);
|
||||
break;
|
||||
case FLAG_MODE_TUNNEL:
|
||||
Hash tunnelRouterHash = new Hash();
|
||||
tunnelRouterHash.readBytes(in);
|
||||
setRouter(tunnelRouterHash);
|
||||
TunnelId id = new TunnelId();
|
||||
id.readBytes(in);
|
||||
setTunnelId(id);
|
||||
break;
|
||||
}
|
||||
|
||||
if (flagDelay(flags)) {
|
||||
long delay = DataHelper.readLong(in, 4);
|
||||
setDelayRequested(true);
|
||||
setDelaySeconds(delay);
|
||||
} else {
|
||||
setDelayRequested(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean flagEncrypted(long flags) {
|
||||
return (0 != (flags & FLAG_ENCRYPTED));
|
||||
}
|
||||
|
||||
private int flagMode(long flags) {
|
||||
long v = flags & FLAG_MODE;
|
||||
v >>>= 5;
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
private boolean flagDelay(long flags) {
|
||||
return (0 != (flags & FLAG_DELAY));
|
||||
}
|
||||
|
||||
private long getFlags() {
|
||||
long val = 0L;
|
||||
if (getEncrypted())
|
||||
val = val | FLAG_ENCRYPTED;
|
||||
long fmode = 0;
|
||||
switch (getDeliveryMode()) {
|
||||
case FLAG_MODE_LOCAL:
|
||||
break;
|
||||
case FLAG_MODE_DESTINATION:
|
||||
fmode = FLAG_MODE_DESTINATION << 5;
|
||||
break;
|
||||
case FLAG_MODE_ROUTER:
|
||||
fmode = FLAG_MODE_ROUTER << 5;
|
||||
break;
|
||||
case FLAG_MODE_TUNNEL:
|
||||
fmode = FLAG_MODE_TUNNEL << 5;
|
||||
break;
|
||||
}
|
||||
val = val | fmode;
|
||||
if (getDelayRequested())
|
||||
val = val | FLAG_DELAY;
|
||||
_log.debug("getFlags() = " + val);
|
||||
return val;
|
||||
}
|
||||
|
||||
private byte[] getAdditionalInfo() throws DataFormatException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
|
||||
try {
|
||||
if (getEncrypted()) {
|
||||
if (_encryptionKey == null) throw new DataFormatException("Encryption key is not set");
|
||||
_encryptionKey.writeBytes(baos);
|
||||
_log.debug("IsEncrypted");
|
||||
} else {
|
||||
_log.debug("Is NOT Encrypted");
|
||||
}
|
||||
switch (getDeliveryMode()) {
|
||||
case FLAG_MODE_LOCAL:
|
||||
_log.debug("mode = local");
|
||||
break;
|
||||
case FLAG_MODE_DESTINATION:
|
||||
if (_destinationHash == null) throw new DataFormatException("Destination hash is not set");
|
||||
_destinationHash.writeBytes(baos);
|
||||
_log.debug("mode = destination, hash = " + _destinationHash);
|
||||
break;
|
||||
case FLAG_MODE_ROUTER:
|
||||
if (_routerHash == null) throw new DataFormatException("Router hash is not set");
|
||||
_routerHash.writeBytes(baos);
|
||||
_log.debug("mode = router, routerHash = " + _routerHash);
|
||||
break;
|
||||
case FLAG_MODE_TUNNEL:
|
||||
if ( (_routerHash == null) || (_tunnelId == null) ) throw new DataFormatException("Router hash or tunnel ID is not set");
|
||||
_routerHash.writeBytes(baos);
|
||||
_tunnelId.writeBytes(baos);
|
||||
_log.debug("mode = tunnel, tunnelId = " + _tunnelId.getTunnelId() + ", routerHash = " + _routerHash);
|
||||
break;
|
||||
}
|
||||
if (getDelayRequested()) {
|
||||
_log.debug("delay requested: " + getDelaySeconds());
|
||||
DataHelper.writeLong(baos, 4, getDelaySeconds());
|
||||
} else {
|
||||
_log.debug("delay NOT requested");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new DataFormatException("Unable to write out additional info", ioe);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ( (_deliveryMode < 0) || (_deliveryMode > FLAG_MODE_TUNNEL) ) throw new DataFormatException("Invalid data: mode = " + _deliveryMode);
|
||||
long flags = getFlags();
|
||||
_log.debug("Write flags: " + flags + " mode: " + getDeliveryMode() + " =?= " + flagMode(flags));
|
||||
byte additionalInfo[] = getAdditionalInfo();
|
||||
DataHelper.writeLong(out, 1, flags);
|
||||
if (additionalInfo != null) {
|
||||
out.write(additionalInfo);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof DeliveryInstructions))
|
||||
return false;
|
||||
DeliveryInstructions instr = (DeliveryInstructions)obj;
|
||||
return (getDelayRequested() == instr.getDelayRequested()) &&
|
||||
(getDelaySeconds() == instr.getDelaySeconds()) &&
|
||||
(getDeliveryMode() == instr.getDeliveryMode()) &&
|
||||
(getEncrypted() == instr.getEncrypted()) &&
|
||||
DataHelper.eq(getDestination(), instr.getDestination()) &&
|
||||
DataHelper.eq(getEncryptionKey(), instr.getEncryptionKey()) &&
|
||||
DataHelper.eq(getRouter(), instr.getRouter()) &&
|
||||
DataHelper.eq(getTunnelId(), instr.getTunnelId());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (int)getDelaySeconds() +
|
||||
getDeliveryMode() +
|
||||
DataHelper.hashCode(getDestination()) +
|
||||
DataHelper.hashCode(getEncryptionKey()) +
|
||||
DataHelper.hashCode(getRouter()) +
|
||||
DataHelper.hashCode(getTunnelId());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[DeliveryInstructions: ");
|
||||
buf.append("\n\tDelivery mode: ");
|
||||
switch (getDeliveryMode()) {
|
||||
case DELIVERY_MODE_LOCAL:
|
||||
buf.append("local");
|
||||
break;
|
||||
case DELIVERY_MODE_DESTINATION:
|
||||
buf.append("destination");
|
||||
break;
|
||||
case DELIVERY_MODE_ROUTER:
|
||||
buf.append("router");
|
||||
break;
|
||||
case DELIVERY_MODE_TUNNEL:
|
||||
buf.append("tunnel");
|
||||
break;
|
||||
}
|
||||
buf.append("\n\tDelay requested: ").append(getDelayRequested());
|
||||
buf.append("\n\tDelay seconds: ").append(getDelaySeconds());
|
||||
buf.append("\n\tDestination: ").append(getDestination());
|
||||
buf.append("\n\tEncrypted: ").append(getEncrypted());
|
||||
buf.append("\n\tEncryption key: ").append(getEncryptionKey());
|
||||
buf.append("\n\tRouter: ").append(getRouter());
|
||||
buf.append("\n\tTunnelId: ").append(getTunnelId());
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
91
router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java
Normal file
91
router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java
Normal file
@ -0,0 +1,91 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message sent back in reply to a message when requested, containing
|
||||
* the private ack id.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DeliveryStatusMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DeliveryStatusMessage.class);
|
||||
public final static int MESSAGE_TYPE = 10;
|
||||
private long _id;
|
||||
private Date _arrival;
|
||||
|
||||
public DeliveryStatusMessage() {
|
||||
setMessageId(-1);
|
||||
setArrival(null);
|
||||
}
|
||||
|
||||
public long getMessageId() { return _id; }
|
||||
public void setMessageId(long id) { _id = id; }
|
||||
|
||||
public Date getArrival() { return _arrival; }
|
||||
public void setArrival(Date arrival) { _arrival = arrival; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_id = DataHelper.readLong(in, 4);
|
||||
_arrival = DataHelper.readDate(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_id < 0) || (_arrival == null) ) throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, _id);
|
||||
DataHelper.writeDate(os, _arrival);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return (int)getMessageId() +
|
||||
DataHelper.hashCode(getArrival());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DeliveryStatusMessage) ) {
|
||||
DeliveryStatusMessage msg = (DeliveryStatusMessage)object;
|
||||
return DataHelper.eq(getMessageId(),msg.getMessageId()) &&
|
||||
DataHelper.eq(getArrival(),msg.getArrival());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DeliveryStatusMessage: ");
|
||||
buf.append("\n\tMessage ID: ").append(getMessageId());
|
||||
buf.append("\n\tArrival: ").append(getArrival());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
62
router/java/src/net/i2p/data/i2np/EndPointPrivateKey.java
Normal file
62
router/java/src/net/i2p/data/i2np/EndPointPrivateKey.java
Normal file
@ -0,0 +1,62 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PrivateKey;
|
||||
|
||||
/**
|
||||
* Contains the private key which matches the EndPointPublicKey which, in turn,
|
||||
* is published on the LeaseSet and used to encrypt messages to the router to
|
||||
* which a Destination is currently connected.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class EndPointPrivateKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(EndPointPrivateKey.class);
|
||||
private PrivateKey _key;
|
||||
|
||||
public EndPointPrivateKey() { setKey(null); }
|
||||
|
||||
public PrivateKey getKey() { return _key; }
|
||||
public void setKey(PrivateKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new PrivateKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof EndPointPublicKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((EndPointPublicKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[EndPointPrivateKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
62
router/java/src/net/i2p/data/i2np/EndPointPublicKey.java
Normal file
62
router/java/src/net/i2p/data/i2np/EndPointPublicKey.java
Normal file
@ -0,0 +1,62 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PublicKey;
|
||||
|
||||
/**
|
||||
* Contains the public key which matches the EndPointPrivateKey. This is
|
||||
* published on the LeaseSet and used to encrypt messages to the router to
|
||||
* which a Destination is currently connected.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class EndPointPublicKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(EndPointPublicKey.class);
|
||||
private PublicKey _key;
|
||||
|
||||
public EndPointPublicKey() { setKey(null); }
|
||||
|
||||
public PublicKey getKey() { return _key; }
|
||||
public void setKey(PublicKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new PublicKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof EndPointPublicKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((EndPointPublicKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[EndPointPublicKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user