forked from I2P_Developers/i2p.i2p
Compare commits
199 Commits
i2p_0_4_1_
...
i2p_0_4_2_
Author | SHA1 | Date | |
---|---|---|---|
daf32a24bc | |||
34ecfd9857 | |||
4838564460 | |||
3dd2f67ff3 | |||
0ccec3dde0 | |||
ad77879caa | |||
27999983cc | |||
48b039940d | |||
84dc7d9d82 | |||
70d6332bad | |||
aec0b0c86a | |||
099f6a88c2 | |||
00a5d42d3d | |||
28f4a2cb67 | |||
1ac18ba10e | |||
1503ee2dfa | |||
484b528d4f | |||
758293dc02 | |||
6cb316b33e | |||
1d31831e7d | |||
ee32b07995 | |||
81f04ca692 | |||
1756997608 | |||
ec11ea4ca7 | |||
a1ebf85e1b | |||
4b2a734cda | |||
97ae8f78a0 | |||
834665c3ba | |||
d969dd2d8d | |||
3cb727561c | |||
cbc89376d3 | |||
66aa29e3d4 | |||
5c72aca5ee | |||
8824815d6d | |||
ad72e5cbdf | |||
b2f183fc17 | |||
9e16bc203a | |||
83c6eac017 | |||
d5b277a536 | |||
77ce6c33e3 | |||
60f8d349cf | |||
f539c3df70 | |||
fe1cf1758c | |||
8c71c26487 | |||
2ce39d1fd4 | |||
88a994b712 | |||
24c8cc1a0c | |||
caf684394c | |||
4b74510450 | |||
0ddcfc423e | |||
b4ac56e204 | |||
3b19ac3942 | |||
af52cad4ea | |||
d88396c1e2 | |||
4c5f7b9451 | |||
e601cedbb8 | |||
fa12dc867f | |||
acfb6c4578 | |||
e52d637092 | |||
2fba055696 | |||
88bb176f3b | |||
499eeb275b | |||
61a8d679bb | |||
2bbde91625 | |||
9ce098ee06 | |||
927ae57d24 | |||
d65c2d3539 | |||
2d9d8f32dc | |||
1a30cd5f4a | |||
f54687f398 | |||
9f4b4c5de1 | |||
33bfa94229 | |||
61e5f190a6 | |||
a4946272d0 | |||
8abd99d134 | |||
97e8ab7c5b | |||
cb930a7ab5 | |||
610f1f7dd4 | |||
516d0b4db8 | |||
df61ae5c6f | |||
9f6584b55e | |||
e4b41f5bb0 | |||
8d0cea93e9 | |||
d294d07919 | |||
153eea2bd5 | |||
571e3c5c13 | |||
a2d268f3d6 | |||
02d456d7a0 | |||
b3626ad86f | |||
9b6eab451f | |||
72be9b5f04 | |||
35e94a7f65 | |||
8e02586cc9 | |||
0b5a640896 | |||
64b5089909 | |||
e0e09bfa45 | |||
8c7f9f2c65 | |||
aff5cea949 | |||
8bd99f699f | |||
b0513fff8a | |||
f10db9d91a | |||
9b5fb17068 | |||
608d713dca | |||
6d5fc8ca21 | |||
8c3145b70f | |||
12a6f3e938 | |||
2c59435762 | |||
7336bf5c55 | |||
2b21b97277 | |||
603bc99a2f | |||
426ede1c99 | |||
21506c1d1b | |||
0b48b18e7e | |||
c8f6d9c7a1 | |||
ed8eced9dd | |||
4a029b7853 | |||
6bd9e58ece | |||
cd075fc8a6 | |||
e733427920 | |||
107da0ae22 | |||
3629d7a32c | |||
71e1152cde | |||
d01ab7fd23 | |||
f46d0a720c | |||
d943b4993a | |||
4a4f57d6ac | |||
085da16268 | |||
306f6b0037 | |||
3780d290fa | |||
ad7dc66f90 | |||
258244fed8 | |||
5f7982540f | |||
b1c0de4b77 | |||
45b3fecfff | |||
9774ded4dd | |||
7ec027854e | |||
b457001b42 | |||
6fc6866eb4 | |||
299e5528bc | |||
881524a5e4 | |||
ffc405138d | |||
f6ff74af16 | |||
73a12d47de | |||
83165df7e5 | |||
30074be5a5 | |||
16715aa309 | |||
53f3802a81 | |||
07626b5cc2 | |||
9ea603caf2 | |||
18ab9b80d2 | |||
71c1cb4e12 | |||
0c049f39d9 | |||
096b807c37 | |||
9018af4765 | |||
323f28e306 | |||
e9dbd00f42 | |||
98b5252a2d | |||
8abf42023a | |||
b792238f3f | |||
2486e5e75f | |||
5f113f1610 | |||
592e9dc3ff | |||
314316cee0 | |||
9cf663063e | |||
9ea9210a4b | |||
2bf1a94608 | |||
7a0236ad29 | |||
4341a0c198 | |||
8071612c12 | |||
ea9cc3da04 | |||
0df588fffb | |||
a622311dbc | |||
1c95ac2470 | |||
6ef22166f9 | |||
1107e50108 | |||
c19355a7b2 | |||
65d415fade | |||
b37313d3f9 | |||
58fcbad20a | |||
b571f331ec | |||
2547d4b3e7 | |||
892786bf0c | |||
0c51f2b583 | |||
d5607ca195 | |||
48cdf17a4f | |||
669a8fae15 | |||
d592936873 | |||
87898dd2f1 | |||
15c227f568 | |||
8de41acfe1 | |||
9680effb9f | |||
40df846e3f | |||
eee94fbf84 | |||
813679ba25 | |||
2b9e16c9c9 | |||
f9bb7f7cff | |||
41e9569094 | |||
336ee07191 | |||
81e0a145f1 |
7
apps/addressbook/.classpath
Normal file
7
apps/addressbook/.classpath
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path=""/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="../jetty/jettylib/javax.servlet.jar"/>
|
||||
<classpathentry kind="output" path=""/>
|
||||
</classpath>
|
17
apps/addressbook/.project
Normal file
17
apps/addressbook/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>addressbook</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
43
apps/addressbook/README.txt
Normal file
43
apps/addressbook/README.txt
Normal file
@ -0,0 +1,43 @@
|
||||
addressbook v2.0.2 - A simple name resolution mechanism for I2P
|
||||
|
||||
addressbook is a simple implementation of subscribable address books for I2P.
|
||||
Addresses are stored in userhosts.txt and a second copy of the address book is
|
||||
placed on your eepsite as hosts.txt.
|
||||
|
||||
subscriptions.txt contains a list of urls to check for new addresses.
|
||||
Since the urls are checked in order, and conflicting addresses are not added,
|
||||
addressbook.subscriptions can be considered to be ranked in order of trust.
|
||||
|
||||
The system created by addressbook is similar to the early days of DNS,
|
||||
when everyone ran a local name server. The major difference is the lack of
|
||||
authority. Name cannot be guaranteed to be globally unique, but in practise
|
||||
they probably will be, for a variety of social reasons.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
i2p with a running http proxy
|
||||
|
||||
Installation and Usage
|
||||
**********************
|
||||
|
||||
1. Unzip addressbook-%ver.zip into your i2p directory.
|
||||
2. Restart your router.
|
||||
|
||||
The addressbook daemon will automatically run while the router is up.
|
||||
|
||||
Aside from the daemon itself, the other elements of the addressbook interface
|
||||
are the config.txt, myhosts.txt, and subscriptions.txt files found in the addressbook
|
||||
directory.
|
||||
|
||||
config.txt is the configuration file for addressbook.
|
||||
|
||||
myhosts.txt is the addressbook master address book. Addresses placed in this file
|
||||
take precidence over those in the router address book and in remote address books.
|
||||
If changes are made to this file, they will be reflected in the router address book
|
||||
and published address book after the next update. Do not make changes directly to the
|
||||
router address book, as they could be lost during an update.
|
||||
|
||||
subscriptions.txt is the subscription list for addressbook. Each entry is an absolute
|
||||
url to a file in hosts.txt format. Since the list is checked in order, url's should be
|
||||
listed in order of trust.
|
46
apps/addressbook/build.xml
Normal file
46
apps/addressbook/build.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="addressbook" default="war" basedir=".">
|
||||
|
||||
<property name="src" value="java/src/addressbook"/>
|
||||
<property name="build" value="build"/>
|
||||
<property name="dist" location="dist"/>
|
||||
<property name="jar" value="addressbook.jar"/>
|
||||
<property name="war" value="addressbook.war"/>
|
||||
<property name="servlet" value="../jetty/jettylib/javax.servlet.jar"/>
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="${build}"/>
|
||||
<delete dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
<target name="distclean" depends="clean" />
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac srcdir="${src}" destdir="${build}" classpath="${servlet}"/>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar basedir="${build}" destfile="${dist}/${jar}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="addressbook.Daemon"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="war" depends="compile">
|
||||
<mkdir dir="${dist}/tmp"/>
|
||||
<mkdir dir="${dist}/tmp/WEB-INF"/>
|
||||
<mkdir dir="${dist}/tmp/WEB-INF/classes"/>
|
||||
<copy todir="${dist}/tmp/WEB-INF/classes">
|
||||
<fileset dir="${build}"/>
|
||||
</copy>
|
||||
<war basedir="${dist}/tmp" webxml="web.xml" destfile="${dist}/${war}"/>
|
||||
<delete dir="${dist}/tmp"/>
|
||||
</target>
|
||||
|
||||
</project>
|
43
apps/addressbook/config.txt
Normal file
43
apps/addressbook/config.txt
Normal file
@ -0,0 +1,43 @@
|
||||
# This is the configuration file for addressbook.
|
||||
#
|
||||
# Options
|
||||
# *******
|
||||
# All paths are realitive to i2p/addressbook. Default value for
|
||||
# each option is given in parentheses.
|
||||
#
|
||||
# proxy_host The hostname of your I2P http proxy.
|
||||
# (localhost)
|
||||
#
|
||||
# proxy_port The port of your I2P http proxy. (4444)
|
||||
#
|
||||
# master_addressbook The path to your master address book, used for local
|
||||
# changes only. (myhosts.txt)
|
||||
#
|
||||
# router_addressbook The path to the address book used by the router.
|
||||
# Contains the addresses from your master address book
|
||||
# and your subscribed address books. (../userhosts.txt)
|
||||
#
|
||||
# published_addressbook The path to the copy of your address book made
|
||||
# available on i2p. (../eepsite/docroot/hosts.txt)
|
||||
#
|
||||
# log The path to your addressbook log. (log.txt)
|
||||
#
|
||||
# subscriptions The path to your subscription file. (subscriptions.txt)
|
||||
#
|
||||
# etags The path to the etags header storage file. (etags)
|
||||
#
|
||||
# last_modified The path to the last-modified header storage file.
|
||||
# (last_modified)
|
||||
#
|
||||
# update_delay The time (in hours) between each update. (1)
|
||||
|
||||
proxy_host=localhost
|
||||
proxy_port=4444
|
||||
master_addressbook=myhosts.txt
|
||||
router_addressbook=../userhosts.txt
|
||||
published_addressbook=../eepsite/docroot/hosts.txt
|
||||
log=log.txt
|
||||
subscriptions=subscriptions.txt
|
||||
etags=etags
|
||||
last_modified=last_modified
|
||||
update_delay=1
|
246
apps/addressbook/java/src/addressbook/AddressBook.java
Normal file
246
apps/addressbook/java/src/addressbook/AddressBook.java
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An address book for storing human readable names mapped to base64 i2p
|
||||
* destinations. AddressBooks can be created from local and remote files, merged
|
||||
* together, and written out to local files.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class AddressBook {
|
||||
|
||||
private String location;
|
||||
|
||||
private Map addresses;
|
||||
|
||||
private boolean modified;
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the Map addresses.
|
||||
*
|
||||
* @param addresses
|
||||
* A Map containing human readable addresses as keys, mapped to
|
||||
* base64 i2p destinations.
|
||||
*/
|
||||
public AddressBook(Map addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the file at url. If the
|
||||
* remote file cannot be read, construct an empty AddressBook
|
||||
*
|
||||
* @param url
|
||||
* A URL pointing at a file with lines in the format "key=value",
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public AddressBook(URL url) {
|
||||
this.location = url.getHost();
|
||||
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(url);
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the Subscription subscription. If the
|
||||
* address book at subscription has not changed since the last time it was
|
||||
* read or cannot be read, return an empty AddressBook.
|
||||
*
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
*/
|
||||
public AddressBook(Subscription subscription) {
|
||||
this.location = subscription.getLocation();
|
||||
|
||||
try {
|
||||
URL url = new URL(subscription.getLocation());
|
||||
HttpURLConnection connection = (HttpURLConnection) url
|
||||
.openConnection();
|
||||
if (subscription.getEtag() != null) {
|
||||
connection.addRequestProperty("If-None-Match", subscription
|
||||
.getEtag());
|
||||
}
|
||||
if (subscription.getLastModified() != null) {
|
||||
connection.addRequestProperty("If-Modified-Since", subscription
|
||||
.getLastModified());
|
||||
}
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
connection.disconnect();
|
||||
this.addresses = new HashMap();
|
||||
return;
|
||||
}
|
||||
if (connection.getHeaderField("ETag") != null) {
|
||||
subscription.setEtag(connection.getHeaderField("ETag"));
|
||||
}
|
||||
if (connection.getHeaderField("Last-Modified") != null) {
|
||||
subscription.setLastModified(connection
|
||||
.getHeaderField("Last-Modified"));
|
||||
}
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new URL(subscription
|
||||
.getLocation()));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the file at file. If the
|
||||
* file cannot be read, construct an empty AddressBook
|
||||
*
|
||||
* @param file
|
||||
* A File pointing at a file with lines in the format
|
||||
* "key=value", where key is a human readable name, and value is
|
||||
* a base64 i2p destination.
|
||||
*/
|
||||
public AddressBook(File file) {
|
||||
this.location = file.toString();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map containing the addresses in the AddressBook.
|
||||
*
|
||||
* @return A Map containing the addresses in the AddressBook, where the key
|
||||
* is a human readable name, and the value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public Map getAddresses() {
|
||||
return this.addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location of the file this AddressBook was constructed from.
|
||||
*
|
||||
* @return A String representing either an abstract path, or a url,
|
||||
* depending on how the instance was constructed.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the contents of the AddressBook.
|
||||
*
|
||||
* @return A String representing the contents of the AddressBook.
|
||||
*/
|
||||
public String toString() {
|
||||
return this.addresses.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with AddressBook other, writing messages about new
|
||||
* addresses or conflicts to log. Addresses in AddressBook other that are
|
||||
* not in this AddressBook are added to this AddressBook. In case of a
|
||||
* conflict, addresses in this AddressBook take precedence
|
||||
*
|
||||
* @param other
|
||||
* An AddressBook to merge with.
|
||||
* @param log
|
||||
* The log to write messages about new addresses or conflicts to.
|
||||
*/
|
||||
public void merge(AddressBook other, Log log) {
|
||||
Iterator otherIter = other.addresses.keySet().iterator();
|
||||
|
||||
while (otherIter.hasNext()) {
|
||||
String otherKey = (String) otherIter.next();
|
||||
String otherValue = (String) other.addresses.get(otherKey);
|
||||
|
||||
if (otherValue.length() >= 516) {
|
||||
if (this.addresses.containsKey(otherKey)) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
log.append("Conflict for " + otherKey + " from "
|
||||
+ other.location
|
||||
+ ". Destination in remote address book is "
|
||||
+ otherValue);
|
||||
}
|
||||
} else {
|
||||
this.addresses.put(otherKey, otherValue);
|
||||
this.modified = true;
|
||||
if (log != null) {
|
||||
log.append("New address " + otherKey
|
||||
+ " added to address book.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with other, without logging.
|
||||
*
|
||||
* @param other
|
||||
* An AddressBook to merge with.
|
||||
*/
|
||||
public void merge(AddressBook other) {
|
||||
this.merge(other, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the contents of this AddressBook out to the File file. If the file
|
||||
* cannot be writen to, this method will silently fail.
|
||||
*
|
||||
* @param file
|
||||
* The file to write the contents of this AddressBook too.
|
||||
*/
|
||||
public void write(File file) {
|
||||
if (this.modified) {
|
||||
try {
|
||||
ConfigParser.write(this.addresses, file);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this AddressBook out to the file it was read from. Requires that
|
||||
* AddressBook was constructed from a file on the local filesystem. If the
|
||||
* file cannot be writen to, this method will silently fail.
|
||||
*/
|
||||
public void write() {
|
||||
this.write(new File(this.location));
|
||||
}
|
||||
}
|
323
apps/addressbook/java/src/addressbook/ConfigParser.java
Normal file
323
apps/addressbook/java/src/addressbook/ConfigParser.java
Normal file
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Iterator;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
* format, and subscription file format.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class ConfigParser {
|
||||
|
||||
/**
|
||||
* Strip the comments from a String. Lines that begin with '#' and ';' are
|
||||
* considered comments, as well as any part of a line after a '#'.
|
||||
*
|
||||
* @param inputLine
|
||||
* A String to strip comments from.
|
||||
* @return A String without comments, but otherwise identical to inputLine.
|
||||
*/
|
||||
public static String stripComments(String inputLine) {
|
||||
if (inputLine.startsWith(";")) {
|
||||
return "";
|
||||
}
|
||||
if (inputLine.split("#").length > 0) {
|
||||
return inputLine.split("#")[0];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of BufferedReader input. input must have
|
||||
* a single key, value pair on each line, in the format: key=value. Lines
|
||||
* starting with '#' or ';' are considered comments, and ignored. Lines that
|
||||
* are obviously not in the format key=value are also ignored.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader with lines in key=value format to parse into
|
||||
* a Map.
|
||||
* @return A Map containing the key, value pairs from input.
|
||||
* @throws IOException
|
||||
* if the BufferedReader cannot be read.
|
||||
*
|
||||
*/
|
||||
public static Map parse(BufferedReader input) throws IOException {
|
||||
Map result = new HashMap();
|
||||
String inputLine;
|
||||
inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim(), splitLine[1].trim());
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the file at url. See
|
||||
* parseBufferedReader for details of the input format.
|
||||
*
|
||||
* @param url
|
||||
* A url pointing to a file to parse.
|
||||
* @return A Map containing the key, value pairs from url.
|
||||
* @throws IOException
|
||||
* if url cannot be read.
|
||||
*/
|
||||
public static Map parse(URL url) throws IOException {
|
||||
InputStream urlStream;
|
||||
urlStream = url.openConnection().getInputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
urlStream));
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. See parseBufferedReader
|
||||
* for details of the input format.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A Map containing the key, value pairs from file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map parse(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the String string. See
|
||||
* parseBufferedReader for details of the input format.
|
||||
*
|
||||
* @param string
|
||||
* A String to parse.
|
||||
* @return A Map containing the key, value pairs from string.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map parse(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. If file cannot be read,
|
||||
* use map instead, and write the result to where file should have been.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param map
|
||||
* A Map to use as the default, if file fails.
|
||||
* @return A Map containing the key, value pairs from file, or if file
|
||||
* cannot be read, map.
|
||||
*/
|
||||
public static Map parse(File file, Map map) {
|
||||
Map result = new HashMap();
|
||||
try {
|
||||
result = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
result = map;
|
||||
try {
|
||||
ConfigParser.write(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the BufferedReader input.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader to parse.
|
||||
* @return A List consisting of one element for each line in input.
|
||||
* @throws IOException
|
||||
* if input cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(BufferedReader input)
|
||||
throws IOException {
|
||||
List result = new LinkedList();
|
||||
String inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine).trim();
|
||||
if (inputLine.length() > 0) {
|
||||
result.add(inputLine);
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the File file.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A List consisting of one element for each line in file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
return ConfigParser.parseSubscriptions(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the String string.
|
||||
*
|
||||
* @param string
|
||||
* A String to parse.
|
||||
* @return A List consisting of one element for each line in string.
|
||||
* @throws IOException
|
||||
* if string cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return ConfigParser.parseSubscriptions(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List using the contents of the File file. If file cannot be
|
||||
* read, use list instead, and write the result to where file should have
|
||||
* been.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param string
|
||||
* A List to use as the default, if file fails.
|
||||
* @return A List consisting of one element for each line in file, or if
|
||||
* file cannot be read, list.
|
||||
*/
|
||||
public static List parseSubscriptions(File file, List list) {
|
||||
List result = new LinkedList();
|
||||
try {
|
||||
result = ConfigParser.parseSubscriptions(file);
|
||||
} catch (IOException exp) {
|
||||
result = list;
|
||||
try {
|
||||
ConfigParser.writeSubscriptions(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to BufferedWriter output. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to output.
|
||||
* @param output
|
||||
* A BufferedWriter to write the Map to.
|
||||
* @throws IOException
|
||||
* if the BufferedWriter cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, BufferedWriter output) throws IOException {
|
||||
Iterator keyIter = map.keySet().iterator();
|
||||
|
||||
while (keyIter.hasNext()) {
|
||||
String key = (String) keyIter.next();
|
||||
output.write(key + "=" + (String) map.get(key));
|
||||
output.newLine();
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to the File file. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to file.
|
||||
* @param file
|
||||
* A File to write the Map to.
|
||||
* @throws IOException
|
||||
* if file cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, File file) throws IOException {
|
||||
ConfigParser
|
||||
.write(map, new BufferedWriter(new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of List list to BufferedReader output. Output is written
|
||||
* with each element of list on a new line.
|
||||
*
|
||||
* @param list
|
||||
* A List to write to file.
|
||||
* @param output
|
||||
* A BufferedReader to write list to.
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List list, BufferedWriter output)
|
||||
throws IOException {
|
||||
Iterator iter = list.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
output.write((String) iter.next());
|
||||
output.newLine();
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of List list to File file. Output is written with each
|
||||
* element of list on a new line.
|
||||
*
|
||||
* @param list
|
||||
* A List to write to file.
|
||||
* @param file
|
||||
* A File to write list to.
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List list, File file)
|
||||
throws IOException {
|
||||
ConfigParser.writeSubscriptions(list, new BufferedWriter(
|
||||
new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
}
|
171
apps/addressbook/java/src/addressbook/Daemon.java
Normal file
171
apps/addressbook/java/src/addressbook/Daemon.java
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main class of addressbook. Performs updates, and runs the main loop.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
* subscribed address books listed in subscriptions.
|
||||
*
|
||||
* @param master
|
||||
* The master AddressBook. This address book is never
|
||||
* overwritten, so it is safe for the user to write to.
|
||||
* @param router
|
||||
* The router AddressBook. This is the address book read by
|
||||
* client applications.
|
||||
* @param published
|
||||
* The published AddressBook. This address book is published on
|
||||
* the user's eepsite so that others may subscribe to it.
|
||||
* @param subscriptions
|
||||
* A SubscriptionList listing the remote address books to update
|
||||
* from.
|
||||
* @param log
|
||||
* The log to write changes and conflicts to.
|
||||
*/
|
||||
public static void update(AddressBook master, AddressBook router,
|
||||
File published, SubscriptionList subscriptions, Log log) {
|
||||
String routerLocation = router.getLocation();
|
||||
master.merge(router);
|
||||
Iterator iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
master.merge((AddressBook) iter.next(), log);
|
||||
}
|
||||
master.write(new File(routerLocation));
|
||||
master.write(published);
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update, using the Map settings to provide the parameters.
|
||||
*
|
||||
* @param settings
|
||||
* A Map containg the parameters needed by update.
|
||||
* @param home
|
||||
* The directory containing addressbook's configuration files.
|
||||
*/
|
||||
public static void update(Map settings, String home) {
|
||||
File masterFile = new File(home, (String) settings
|
||||
.get("master_addressbook"));
|
||||
File routerFile = new File(home, (String) settings
|
||||
.get("router_addressbook"));
|
||||
File published = new File(home, (String) settings
|
||||
.get("published_addressbook"));
|
||||
File subscriptionFile = new File(home, (String) settings
|
||||
.get("subscriptions"));
|
||||
File logFile = new File(home, (String) settings.get("log"));
|
||||
File etagsFile = new File(home, (String) settings.get("etags"));
|
||||
File lastModifiedFile = new File(home, (String) settings
|
||||
.get("last_modified"));
|
||||
|
||||
AddressBook master = new AddressBook(masterFile);
|
||||
AddressBook router = new AddressBook(routerFile);
|
||||
|
||||
List defaultSubs = new LinkedList();
|
||||
defaultSubs.add("http://dev.i2p/i2p/hosts.txt");
|
||||
defaultSubs.add("http://duck.i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs);
|
||||
Log log = new Log(logFile);
|
||||
|
||||
Daemon.update(master, router, published, subscriptions, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings, set the proxy, then enter into the main loop. The main
|
||||
* loop performs an immediate update, and then an update every number of
|
||||
* hours, as configured in the settings file.
|
||||
*
|
||||
* @param args
|
||||
* Command line arguments. If there are any arguments provided,
|
||||
* the first is taken as addressbook's home directory, and the
|
||||
* others are ignored.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String settingsLocation = "config.txt";
|
||||
Map settings = new HashMap();
|
||||
String home;
|
||||
if (args.length > 0) {
|
||||
home = args[0];
|
||||
} else {
|
||||
home = ".";
|
||||
}
|
||||
|
||||
Map defaultSettings = new HashMap();
|
||||
defaultSettings.put("proxy_host", "localhost");
|
||||
defaultSettings.put("proxy_port", "4444");
|
||||
defaultSettings.put("master_addressbook", "myhosts.txt");
|
||||
defaultSettings.put("router_addressbook", "../userhosts.txt");
|
||||
defaultSettings.put("published_addressbook", "../eepsite/docroot/hosts.txt");
|
||||
defaultSettings.put("log", "log.txt");
|
||||
defaultSettings.put("subscriptions", "subscriptions.txt");
|
||||
defaultSettings.put("etags", "etags");
|
||||
defaultSettings.put("last_modified", "last_modified");
|
||||
defaultSettings.put("update_delay", "1");
|
||||
|
||||
File homeFile = new File(home);
|
||||
if (!homeFile.exists()) {
|
||||
boolean created = homeFile.mkdirs();
|
||||
if (created)
|
||||
System.out.println("INFO: Addressbook directory " + homeFile.getName() + " created");
|
||||
else
|
||||
System.out.println("ERROR: Addressbook directory " + homeFile.getName() + " could not be created");
|
||||
}
|
||||
|
||||
File settingsFile = new File(homeFile, settingsLocation);
|
||||
|
||||
while (true) {
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
|
||||
System.setProperty("proxySet", "true");
|
||||
System.setProperty("http.proxyHost", (String) settings
|
||||
.get("proxy_host"));
|
||||
System.setProperty("http.proxyPort", (String) settings
|
||||
.get("proxy_port"));
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
}
|
||||
|
||||
Daemon.update(settings, home);
|
||||
try {
|
||||
Thread.sleep(delay * 60 * 60 * 1000);
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
apps/addressbook/java/src/addressbook/DaemonThread.java
Normal file
53
apps/addressbook/java/src/addressbook/DaemonThread.java
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
/**
|
||||
* A thread that waits five minutes, then runs the addressbook daemon.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class DaemonThread extends Thread {
|
||||
|
||||
private String[] args;
|
||||
|
||||
/**
|
||||
* Construct a DaemonThread with the command line arguments args.
|
||||
* @param args
|
||||
* A String array to pass to Daemon.main().
|
||||
*/
|
||||
public DaemonThread(String[] args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(5 * 60 * 1000);
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
76
apps/addressbook/java/src/addressbook/Log.java
Normal file
76
apps/addressbook/java/src/addressbook/Log.java
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A simple log with automatic time stamping.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* Construct a Log instance that writes to the File file.
|
||||
*
|
||||
* @param file
|
||||
* A File for the log to write to.
|
||||
*/
|
||||
public Log(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write entry to a new line in the log, with appropriate time stamp.
|
||||
*
|
||||
* @param entry
|
||||
* A String containing a message to append to the log.
|
||||
*/
|
||||
public void append(String entry) {
|
||||
try {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(this.file,
|
||||
true));
|
||||
String timestamp = new Date().toString();
|
||||
bw.write(timestamp + " -- " + entry);
|
||||
bw.newLine();
|
||||
bw.close();
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the File that the Log is writing to.
|
||||
*
|
||||
* @return The File that the log is writing to.
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
}
|
61
apps/addressbook/java/src/addressbook/Servlet.java
Normal file
61
apps/addressbook/java/src/addressbook/Servlet.java
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
/**
|
||||
* A wrapper for addressbook to allow it to be started as a web application.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Servlet extends GenericServlet {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
|
||||
*/
|
||||
public void service(ServletRequest request, ServletResponse response) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
|
||||
*/
|
||||
public void init(ServletConfig config) {
|
||||
try {
|
||||
super.init(config);
|
||||
} catch (ServletException exp) {
|
||||
}
|
||||
String[] args = new String[1];
|
||||
args[0] = config.getInitParameter("home");
|
||||
DaemonThread thread = new DaemonThread(args);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
System.out.println("INFO: Starting Addressbook " + Daemon.VERSION);
|
||||
System.out.println("INFO: config root under " + args[0]);
|
||||
}
|
||||
|
||||
}
|
105
apps/addressbook/java/src/addressbook/Subscription.java
Normal file
105
apps/addressbook/java/src/addressbook/Subscription.java
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
/**
|
||||
* A subscription to a remote address book.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Subscription {
|
||||
|
||||
private String location;
|
||||
|
||||
private String etag;
|
||||
|
||||
private String lastModified;
|
||||
|
||||
/**
|
||||
* Construct a Subscription pointing to the address book at location, that
|
||||
* was last read at the time represented by etag and lastModified.
|
||||
*
|
||||
* @param location
|
||||
* A String representing a url to a remote address book.
|
||||
* @param etag
|
||||
* The etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
* @param lastModified
|
||||
* the last-modified header we recieved the last time we read
|
||||
* this subscription.
|
||||
*/
|
||||
public Subscription(String location, String etag, String lastModified) {
|
||||
this.location = location;
|
||||
this.etag = etag;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location this Subscription points at.
|
||||
*
|
||||
* @return A String representing a url to a remote address book.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
*
|
||||
* @return A String containing the etag header.
|
||||
*/
|
||||
public String getEtag() {
|
||||
return this.etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the etag header.
|
||||
*
|
||||
* @param etag
|
||||
* A String containing the etag header.
|
||||
*/
|
||||
public void setEtag(String etag) {
|
||||
this.etag = etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last-modified header that we recieved the last time we read
|
||||
* this subscription.
|
||||
*
|
||||
* @return A String containing the last-modified header.
|
||||
*/
|
||||
public String getLastModified() {
|
||||
return this.lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last-modified header.
|
||||
*
|
||||
* @param lastModified
|
||||
* A String containing the last-modified header.
|
||||
*/
|
||||
public void setLastModified(String lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
* returns AddressBook objects, and not Subscription objects.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class SubscriptionIterator implements Iterator {
|
||||
|
||||
private Iterator subIterator;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
|
||||
*
|
||||
* @param subscriptions
|
||||
* List of Subscription objects that represent address books.
|
||||
*/
|
||||
public SubscriptionIterator(List subscriptions) {
|
||||
this.subIterator = subscriptions.iterator();
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return subIterator.hasNext();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
*/
|
||||
public Object next() {
|
||||
Subscription sub = (Subscription) subIterator.next();
|
||||
return new AddressBook(sub);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#remove()
|
||||
*/
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
123
apps/addressbook/java/src/addressbook/SubscriptionList.java
Normal file
123
apps/addressbook/java/src/addressbook/SubscriptionList.java
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A list of Subscriptions loaded from a file.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class SubscriptionList {
|
||||
|
||||
private List subscriptions;
|
||||
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionList using the urls from locationsFile and, if
|
||||
* available, the etags and last-modified headers loaded from etagsFile and
|
||||
* lastModifiedFile.
|
||||
*
|
||||
* @param locationsFile
|
||||
* A file containing one url on each line.
|
||||
* @param etagsFile
|
||||
* A file containg the etag headers used for conditional GET. The
|
||||
* file is in the format "url=etag".
|
||||
* @param lastModifiedFile
|
||||
* A file containg the last-modified headers used for conditional
|
||||
* GET. The file is in the format "url=leastmodified".
|
||||
*/
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, List defaultSubs) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
List locations;
|
||||
Map etags;
|
||||
Map lastModified;
|
||||
String location;
|
||||
locations = ConfigParser.parseSubscriptions(locationsFile, defaultSubs);
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
etags = new HashMap();
|
||||
}
|
||||
try {
|
||||
lastModified = ConfigParser.parse(lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
lastModified = new HashMap();
|
||||
}
|
||||
Iterator iter = locations.iterator();
|
||||
while (iter.hasNext()) {
|
||||
location = (String) iter.next();
|
||||
subscriptions.add(new Subscription(location, (String) etags
|
||||
.get(location), (String) lastModified.get(location)));
|
||||
}
|
||||
|
||||
iter = this.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over the AddressBooks represented by the Subscriptions
|
||||
* in this SubscriptionList.
|
||||
*
|
||||
* @return A SubscriptionIterator.
|
||||
*/
|
||||
public SubscriptionIterator iterator() {
|
||||
return new SubscriptionIterator(this.subscriptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the etag and last-modified headers for each Subscription to files.
|
||||
*/
|
||||
public void write() {
|
||||
Iterator iter = this.subscriptions.iterator();
|
||||
Subscription sub;
|
||||
Map etags = new HashMap();
|
||||
Map lastModified = new HashMap();
|
||||
while (iter.hasNext()) {
|
||||
sub = (Subscription) iter.next();
|
||||
if (sub.getEtag() != null) {
|
||||
etags.put(sub.getLocation(), sub.getEtag());
|
||||
}
|
||||
if (sub.getLastModified() != null) {
|
||||
lastModified.put(sub.getLocation(), sub.getLastModified());
|
||||
}
|
||||
}
|
||||
try {
|
||||
ConfigParser.write(etags, this.etagsFile);
|
||||
ConfigParser.write(lastModified, this.lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
10
apps/addressbook/myhosts.txt
Normal file
10
apps/addressbook/myhosts.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# addressbook master address book. Addresses placed in this file take precidence
|
||||
# over those in the router address book and in remote address books. If changes
|
||||
# are made to this file, they will be reflected in the router address book and
|
||||
# published address book after the next update.
|
||||
#
|
||||
# Do not make changes directly to the router address book, as they could be lost
|
||||
# during an update.
|
||||
#
|
||||
# This file takes addresses in the hosts.txt format, i.e.
|
||||
# example.i2p=somereallylongbase64thingAAAA
|
7
apps/addressbook/subscriptions.txt
Normal file
7
apps/addressbook/subscriptions.txt
Normal file
@ -0,0 +1,7 @@
|
||||
# Subscription list for addressbook
|
||||
#
|
||||
# Each entry is an absolute url to a file in hosts.txt format.
|
||||
# Since the list is checked in order, url's should be listed in order of trust.
|
||||
#
|
||||
http://dev.i2p/i2p/hosts.txt
|
||||
http://duck.i2p/hosts.txt
|
16
apps/addressbook/web.xml
Normal file
16
apps/addressbook/web.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
|
||||
<web-app>
|
||||
<servlet>
|
||||
<servlet-name>addressbook</servlet-name>
|
||||
<servlet-class>addressbook.Servlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>home</param-name>
|
||||
<param-value>./addressbook</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
</web-app>
|
@ -274,9 +274,9 @@ public class PeerData {
|
||||
|
||||
_lostRate.addData(numTimedOut, 0);
|
||||
|
||||
_receiveRate.coallesceStats();
|
||||
_sendRate.coallesceStats();
|
||||
_lostRate.coallesceStats();
|
||||
_receiveRate.coalesceStats();
|
||||
_sendRate.coalesceStats();
|
||||
_lostRate.coalesceStats();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
|
||||
@ -409,4 +409,4 @@ public class PeerData {
|
||||
_wasPonged = pong;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +229,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runClientOptions(args, l);
|
||||
} else if ("server".equals(cmdname)) {
|
||||
runServer(args, l);
|
||||
} else if ("httpserver".equals(cmdname)) {
|
||||
runHttpServer(args, l);
|
||||
} else if ("textserver".equals(cmdname)) {
|
||||
runTextServer(args, l);
|
||||
} else if ("client".equals(cmdname)) {
|
||||
@ -281,10 +283,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("owndest yes|no");
|
||||
l.log("ping <args>");
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log("gentextkeys");
|
||||
l.log("client <port> <pubkey>|file:<pubkeyfile>");
|
||||
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile>");
|
||||
l.log("httpclient <port>");
|
||||
l.log("lookup <name>");
|
||||
l.log("quit");
|
||||
@ -370,6 +373,65 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the HTTP server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the specified file, replacing the HTTP headers
|
||||
* so that the Host: specified is the one spoofed. <p />
|
||||
*
|
||||
* 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, spoofedHost, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHttpServer(String args[], Logging l) {
|
||||
if (args.length == 4) {
|
||||
InetAddress serverHost = null;
|
||||
int portNum = -1;
|
||||
File privKeyFile = null;
|
||||
try {
|
||||
serverHost = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
portNum = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
String spoofedHost = args[2];
|
||||
|
||||
privKeyFile = new File(args[3]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
serv.startRunning();
|
||||
addtask(serv);
|
||||
notifyEvent("serverTaskId", new Integer(serv.getId()));
|
||||
return;
|
||||
} else {
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
|
||||
l.log(" creates an HTTP server that sends all incoming data\n"
|
||||
+ " of its destination to host:port., filtering the HTTP\n"
|
||||
+ " headers so it looks like the request is to the spoofed host.");
|
||||
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. <p />
|
||||
@ -449,9 +511,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("client <port> <pubkey>|file:<pubkeyfile>");
|
||||
l.log("client <port> <pubkey>[,<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.");
|
||||
+ " use 0 as port to get a free port assigned. If you specify\n"
|
||||
+ " a comma delimited list of pubkeys, it will rotate among them\n"
|
||||
+ " randomlyl");
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,11 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
@ -15,15 +19,17 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelClient.class);
|
||||
|
||||
protected Destination dest;
|
||||
/** list of Destination objects that we point at */
|
||||
protected List dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
/**
|
||||
* @param destinations comma delimited list of peers we target
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
public I2PTunnelClient(int localPort, String destination, Logging l,
|
||||
public I2PTunnelClient(int localPort, String destinations, Logging l,
|
||||
boolean ownDest, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "SynSender", tunnel);
|
||||
@ -33,19 +39,28 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
return;
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ",");
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String destination = tok.nextToken();
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(dest);
|
||||
} catch (DataFormatException dfe) {
|
||||
l.log("Bad format parsing \"" + destination + "\"");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
l.log("Bad format in destination \"" + destination + "\".");
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
l.log("No target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> " + destination);
|
||||
setName(getLocalPort() + " -> " + destinations);
|
||||
|
||||
startRunning();
|
||||
|
||||
@ -56,14 +71,34 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
public long getReadTimeout() { return readTimeout; }
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
Destination dest = pickDestination();
|
||||
I2PSocket i2ps = null;
|
||||
try {
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
|
||||
} catch (Exception ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
mySockets.remove(sockLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Destination pickDestination() {
|
||||
int size = dests.size();
|
||||
if (size <= 0) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("No client targets?!");
|
||||
return null;
|
||||
}
|
||||
if (size == 1) // skip the rand in the most common case
|
||||
return (Destination)dests.get(0);
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return (Destination)dests.get(index);
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,10 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
@ -26,6 +28,7 @@ import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
|
||||
@ -33,13 +36,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private static final Log _log = new Log(I2PTunnelClientBase.class);
|
||||
protected Logging l;
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
|
||||
private static volatile long __clientId = 0;
|
||||
protected long _clientId;
|
||||
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
|
||||
private I2PSocketManager sockMgr;
|
||||
private List mySockets = new ArrayList();
|
||||
protected I2PSocketManager sockMgr;
|
||||
protected List mySockets = new ArrayList();
|
||||
|
||||
protected Destination dest = null;
|
||||
private int localPort;
|
||||
@ -57,6 +60,32 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
private String handlerName;
|
||||
|
||||
private Object conLock = new Object();
|
||||
|
||||
/** List of Socket for those accept()ed but not yet started up */
|
||||
private List _waitingSockets;
|
||||
/** How many connections will we allow to be in the process of being built at once? */
|
||||
private int _numConnectionBuilders;
|
||||
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
|
||||
private int _maxWaitTime;
|
||||
|
||||
/**
|
||||
* How many concurrent connections this I2PTunnel instance will allow to be
|
||||
* in the process of connecting (or if less than 1, there is no limit)?
|
||||
*/
|
||||
public static final String PROP_NUM_CONNECTION_BUILDERS = "i2ptunnel.numConnectionBuilders";
|
||||
/**
|
||||
* How long will we let a socket wait after being accept()ed without getting
|
||||
* pumped through a connection builder (in milliseconds). If this time is
|
||||
* reached, the socket is unceremoniously closed and discarded. If the max
|
||||
* wait time is less than 1, there is no limit.
|
||||
*
|
||||
*/
|
||||
public static final String PROP_MAX_WAIT_TIME = "i2ptunnel.maxWaitTime";
|
||||
|
||||
private static final int DEFAULT_NUM_CONNECTION_BUILDERS = 5;
|
||||
private static final int DEFAULT_MAX_WAIT_TIME = 30*1000;
|
||||
|
||||
//public I2PTunnelClientBase(int localPort, boolean ownDest,
|
||||
// Logging l) {
|
||||
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
|
||||
@ -96,7 +125,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
t.start();
|
||||
open = true;
|
||||
synchronized (this) {
|
||||
while (!listenerReady) {
|
||||
while (!listenerReady && open) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
@ -105,14 +134,47 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
configurePool(tunnel);
|
||||
|
||||
if (open && listenerReady) {
|
||||
l.log("Ready! Port " + getLocalPort());
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Error!");
|
||||
l.log("Error listening - please see the logs!");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* build and configure the pool handling accept()ed but not yet
|
||||
* established connections
|
||||
*
|
||||
*/
|
||||
private void configurePool(I2PTunnel tunnel) {
|
||||
_waitingSockets = new ArrayList(4);
|
||||
|
||||
Properties opts = tunnel.getClientOptions();
|
||||
String maxWait = opts.getProperty(PROP_MAX_WAIT_TIME, DEFAULT_MAX_WAIT_TIME+"");
|
||||
try {
|
||||
_maxWaitTime = Integer.parseInt(maxWait);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_maxWaitTime = DEFAULT_MAX_WAIT_TIME;
|
||||
}
|
||||
|
||||
String numBuild = opts.getProperty(PROP_NUM_CONNECTION_BUILDERS, DEFAULT_NUM_CONNECTION_BUILDERS+"");
|
||||
try {
|
||||
_numConnectionBuilders = Integer.parseInt(numBuild);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_numConnectionBuilders = DEFAULT_NUM_CONNECTION_BUILDERS;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numConnectionBuilders; i++) {
|
||||
String name = "ClientBuilder" + _clientId + '.' + i;
|
||||
I2PThread b = new I2PThread(new TunnelConnectionBuilder(), name);
|
||||
b.setDaemon(true);
|
||||
b.start();
|
||||
}
|
||||
}
|
||||
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
@ -181,9 +243,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
private I2PSocketOptions getDefaultOptions() {
|
||||
I2PSocketOptions opts = new I2PSocketOptions();
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
@ -227,7 +304,14 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
public final void run() {
|
||||
try {
|
||||
InetAddress addr = getListenHost(l);
|
||||
if (addr == null) return;
|
||||
if (addr == null) {
|
||||
open = false;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
return;
|
||||
}
|
||||
ss = new ServerSocket(localPort, 0, addr);
|
||||
|
||||
// If a free port was requested, find out what we got
|
||||
@ -258,8 +342,18 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
manageConnection(s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error listening for connections", ex);
|
||||
_log.error("Error listening for connections on " + localPort, ex);
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
synchronized (sockLock) {
|
||||
mySockets.clear();
|
||||
}
|
||||
open = false;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
synchronized (_waitingSockets) {
|
||||
_waitingSockets.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,7 +363,52 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* @param s Socket to take care of
|
||||
*/
|
||||
protected void manageConnection(Socket s) {
|
||||
new ClientConnectionRunner(s, handlerName);
|
||||
if (s == null) return;
|
||||
if (_numConnectionBuilders <= 0) {
|
||||
new I2PThread(new BlockingRunner(s), "Clinet run").start();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_maxWaitTime > 0)
|
||||
SimpleTimer.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
|
||||
|
||||
synchronized (_waitingSockets) {
|
||||
_waitingSockets.add(s);
|
||||
_waitingSockets.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking runner, used during the connection establishment whenever we
|
||||
* are not using the queued builders.
|
||||
*
|
||||
*/
|
||||
private class BlockingRunner implements Runnable {
|
||||
private Socket _s;
|
||||
public BlockingRunner(Socket s) { _s = s; }
|
||||
public void run() {
|
||||
clientConnectionRun(_s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove and close the socket from the waiting list, if it is still there.
|
||||
*
|
||||
*/
|
||||
private class CloseEvent implements SimpleTimer.TimedEvent {
|
||||
private Socket _s;
|
||||
public CloseEvent(Socket s) { _s = s; }
|
||||
public void timeReached() {
|
||||
boolean stillWaiting = false;
|
||||
synchronized (_waitingSockets) {
|
||||
stillWaiting = _waitingSockets.remove(_s);
|
||||
}
|
||||
if (stillWaiting) {
|
||||
try { _s.close(); } catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Closed a waiting socket because of backlog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
@ -301,8 +440,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
l.log("Client closed.");
|
||||
open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void closeSocket(Socket s) {
|
||||
@ -312,20 +453,30 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
_log.error("Could not close socket", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __runnerId = 0;
|
||||
|
||||
public class ClientConnectionRunner extends I2PThread {
|
||||
private Socket s;
|
||||
|
||||
public ClientConnectionRunner(Socket s, String name) {
|
||||
this.s = s;
|
||||
setName(name + '.' + (++__runnerId));
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
clientConnectionRun(s);
|
||||
/**
|
||||
* Pool runner pulling sockets off the waiting list and pushing them
|
||||
* through clientConnectionRun. This dies when the I2PTunnel instance
|
||||
* is closed.
|
||||
*
|
||||
*/
|
||||
private class TunnelConnectionBuilder implements Runnable {
|
||||
public void run() {
|
||||
Socket s = null;
|
||||
while (open) {
|
||||
try {
|
||||
synchronized (_waitingSockets) {
|
||||
if (_waitingSockets.size() <= 0)
|
||||
_waitingSockets.wait();
|
||||
else
|
||||
s = (Socket)_waitingSockets.remove(0);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
if (s != null)
|
||||
clientConnectionRun(s);
|
||||
s = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,12 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
@ -142,14 +144,48 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
|
||||
//if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
|
||||
// defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
|
||||
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
|
||||
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
OutputStream out = null;
|
||||
String targetRequest = null;
|
||||
boolean usingWWWProxy = false;
|
||||
String currentProxy = null;
|
||||
InactivityTimeoutThread timeoutThread = null;
|
||||
long requestId = ++__requestId;
|
||||
try {
|
||||
out = s.getOutputStream();
|
||||
@ -295,18 +331,32 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Setting host = " + host);
|
||||
} else if (line.startsWith("User-Agent: ")) {
|
||||
line = "User-Agent: MYOB/6.66 (AN/ON)";
|
||||
// always stripped, added back at the end
|
||||
line = null;
|
||||
continue;
|
||||
} else if (line.startsWith("Accept")) {
|
||||
// strip the accept-blah headers, as they vary dramatically from
|
||||
// browser to browser
|
||||
line = null;
|
||||
continue;
|
||||
} else if (line.startsWith("Referer: ")) {
|
||||
// Shouldn't we be more specific, like accepting in-site referers ?
|
||||
line = "Referer: i2p";
|
||||
//line = "Referer: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("Via: ")) {
|
||||
line = "Via: i2p";
|
||||
//line = "Via: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("From: ")) {
|
||||
line = "From: i2p";
|
||||
//line = "From: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
}
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
@ -348,25 +398,27 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return;
|
||||
}
|
||||
String remoteID;
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
|
||||
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
|
||||
// dont want to hard link to here
|
||||
opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
|
||||
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data);
|
||||
timeoutThread = new InactivityTimeoutThread(runner, out, targetRequest, usingWWWProxy, currentProxy, s, requestId);
|
||||
timeoutThread.start();
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (IOException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (I2PException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
@ -374,91 +426,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private static final long INACTIVITY_TIMEOUT = 120 * 1000;
|
||||
private static volatile long __timeoutId = 0;
|
||||
|
||||
private class InactivityTimeoutThread extends I2PThread {
|
||||
|
||||
private Socket s;
|
||||
private I2PTunnelRunner _runner;
|
||||
private OutputStream _out;
|
||||
private String _targetRequest;
|
||||
private boolean _useWWWProxy;
|
||||
private String _currentProxy;
|
||||
private long _requestId;
|
||||
private boolean _disabled;
|
||||
private Object _disableLock = new Object();
|
||||
|
||||
public InactivityTimeoutThread(I2PTunnelRunner runner, OutputStream out, String targetRequest,
|
||||
boolean useWWWProxy, String currentProxy, Socket s, long requestId) {
|
||||
this.s = s;
|
||||
_runner = runner;
|
||||
_out = out;
|
||||
_targetRequest = targetRequest;
|
||||
_useWWWProxy = useWWWProxy;
|
||||
_currentProxy = currentProxy;
|
||||
_disabled = false;
|
||||
_requestId = requestId;
|
||||
long timeoutId = ++__timeoutId;
|
||||
setName("InactivityThread " + getPrefix(requestId) + timeoutId);
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
_disabled = true;
|
||||
synchronized (_disableLock) {
|
||||
_disableLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!_disabled) {
|
||||
if (_runner.isFinished()) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(_requestId) + "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(getPrefix(_requestId) + "HTTP client request timed out (lastActivity: "
|
||||
+ new Date(_runner.getLastActivityOn()) + ", startedOn: "
|
||||
+ new Date(_runner.getStartedOn()) + ")");
|
||||
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(getPrefix(_requestId) + "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, _currentProxy);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(_requestId) + "Error writing out the 'timeout' message", ioe);
|
||||
}
|
||||
} else {
|
||||
_log.warn(getPrefix(_requestId) + "Client disconnected before we could say we timed out");
|
||||
}
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
private final static String getHostName(String host) {
|
||||
if (host == null) return null;
|
||||
try {
|
||||
@ -470,6 +437,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private class OnTimeout implements Runnable {
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
private boolean _usingProxy;
|
||||
private String _wwwProxy;
|
||||
private long _requestId;
|
||||
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
|
||||
_socket = s;
|
||||
_out = out;
|
||||
_target = target;
|
||||
_usingProxy = usingProxy;
|
||||
_wwwProxy = wwwProxy;
|
||||
_requestId = id;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Timeout occured requesting " + _target);
|
||||
handleHTTPClientException(new RuntimeException("Timeout"), _out,
|
||||
_target, _usingProxy, _wwwProxy, _requestId);
|
||||
closeSocket(_socket);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
if (out != null) {
|
||||
|
@ -0,0 +1,162 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
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.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple extension to the I2PTunnelServer that filters the HTTP
|
||||
* headers sent from the client to the server, replacing the Host
|
||||
* header with whatever this instance has been configured with.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private final static Log _log = new Log(I2PTunnelHTTPServer.class);
|
||||
/** what Host: should we seem to be to the webserver? */
|
||||
private String _spoofHost;
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
while (true) {
|
||||
I2PSocket i2ps = i2pss.accept();
|
||||
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
|
||||
I2PThread t = new I2PThread(new Handler(i2ps));
|
||||
t.start();
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async handler to keep .accept() from blocking too long.
|
||||
* todo: replace with a thread pool so we dont get overrun by threads if/when
|
||||
* receiving a lot of connection requests concurrently.
|
||||
*
|
||||
*/
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _handleSocket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_handleSocket = socket;
|
||||
}
|
||||
public void run() {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
_handleSocket.setReadTimeout(readTimeout);
|
||||
String modifiedHeader = getModifiedHeader();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null, modifiedHeader.getBytes(), null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
_handleSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: "
|
||||
+ (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
private String getModifiedHeader() throws IOException {
|
||||
InputStream in = _handleSocket.getInputStream();
|
||||
|
||||
StringBuffer command = new StringBuffer(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
return formatHeaders(headers, command);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString()).append('\n');
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String val = headers.getProperty(name);
|
||||
buf.append(name).append(": ").append(val).append('\n');
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private Properties readHeaders(InputStream in, StringBuffer command) throws IOException {
|
||||
Properties headers = new Properties();
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
|
||||
boolean ok = DataHelper.readLine(in, command);
|
||||
if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the http command [" + command.toString() + "]");
|
||||
|
||||
while (true) {
|
||||
buf.setLength(0);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) {
|
||||
// end of headers reached
|
||||
return headers;
|
||||
} else {
|
||||
int split = buf.indexOf(": ");
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
String name = buf.substring(0, split);
|
||||
String value = buf.substring(split+2); // ": "
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@ -38,23 +40,43 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
Object slock, finishLock = new Object();
|
||||
boolean finished = false;
|
||||
HashMap ostreams, sockets;
|
||||
I2PSession session;
|
||||
byte[] initialData;
|
||||
byte[] initialI2PData;
|
||||
byte[] initialSocketData;
|
||||
/** when the last data was sent/received (or -1 if never) */
|
||||
private long lastActivityOn;
|
||||
/** when the runner started up */
|
||||
private long startedOn;
|
||||
private List sockList;
|
||||
/** if we die before receiving any data, run this job */
|
||||
private Runnable onTimeout;
|
||||
private long totalSent;
|
||||
private long totalReceived;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData) {
|
||||
private volatile long __forwarderId;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) {
|
||||
this(s, i2ps, slock, initialI2PData, null, sockList, null);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList) {
|
||||
this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) {
|
||||
this.sockList = sockList;
|
||||
this.s = s;
|
||||
this.i2ps = i2ps;
|
||||
this.slock = slock;
|
||||
this.initialData = initialData;
|
||||
this.initialI2PData = initialI2PData;
|
||||
this.initialSocketData = initialSocketData;
|
||||
this.onTimeout = onTimeout;
|
||||
lastActivityOn = -1;
|
||||
startedOn = Clock.getInstance().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2PTunnelRunner started");
|
||||
_runnerId = ++__runnerId;
|
||||
__forwarderId = i2ps.hashCode();
|
||||
setName("I2PTunnelRunner " + _runnerId);
|
||||
start();
|
||||
}
|
||||
@ -93,48 +115,77 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
OutputStream out = s.getOutputStream(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
i2ps.setSocketErrorListener(this);
|
||||
InputStream i2pin = i2ps.getInputStream();
|
||||
OutputStream i2pout = new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialData != null) {
|
||||
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialI2PData != null) {
|
||||
synchronized (slock) {
|
||||
i2pout.write(initialData);
|
||||
i2pout.flush();
|
||||
i2pout.write(initialI2PData);
|
||||
//i2pout.flush();
|
||||
}
|
||||
}
|
||||
Thread t1 = new StreamForwarder(in, i2pout);
|
||||
Thread t2 = new StreamForwarder(i2pin, out);
|
||||
if (initialSocketData != null) {
|
||||
out.write(initialSocketData);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Initial data " + (initialI2PData != null ? initialI2PData.length : 0)
|
||||
+ " written to I2P, " + (initialSocketData != null ? initialSocketData.length : 0)
|
||||
+ " written to the socket, starting forwarders");
|
||||
Thread t1 = new StreamForwarder(in, i2pout, true);
|
||||
Thread t2 = new StreamForwarder(i2pin, out, false);
|
||||
synchronized (finishLock) {
|
||||
while (!finished) {
|
||||
finishLock.wait();
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("At least one forwarder completed, closing and joining");
|
||||
|
||||
// this task is useful for the httpclient
|
||||
if (onTimeout != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("runner has a timeout job, totalReceived = " + totalReceived
|
||||
+ " totalSent = " + totalSent + " job = " + onTimeout);
|
||||
if ( (totalSent <= 0) && (totalReceived <= 0) )
|
||||
onTimeout.run();
|
||||
}
|
||||
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
s = null;
|
||||
i2ps.close();
|
||||
i2ps = null;
|
||||
t1.join();
|
||||
t2.join();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
} catch (InterruptedException ex) {
|
||||
_log.error("Interrupted", ex);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.debug("Error forwarding", ex);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error forwarding", ex);
|
||||
} catch (Exception e) {
|
||||
_log.error("Internal error", e);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Internal error", e);
|
||||
} finally {
|
||||
removeRef();
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
if (i2ps != null) i2ps.close();
|
||||
if (s != null)
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.error("Could not close socket", ex);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close java socket", ex);
|
||||
}
|
||||
if (i2ps != null) {
|
||||
try {
|
||||
i2ps.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close I2PSocket", ex);
|
||||
}
|
||||
i2ps.setSocketErrorListener(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void errorOccurred() {
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
@ -142,69 +193,116 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
}
|
||||
|
||||
private volatile long __forwarderId = 0;
|
||||
private void removeRef() {
|
||||
if (sockList != null) {
|
||||
synchronized (slock) {
|
||||
boolean removed = sockList.remove(i2ps);
|
||||
//System.out.println("Removal of i2psocket " + i2ps + " successful? "
|
||||
// + removed + " remaining: " + sockList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StreamForwarder extends I2PThread {
|
||||
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
String direction;
|
||||
private boolean _toI2P;
|
||||
private ByteCache _cache;
|
||||
|
||||
private StreamForwarder(InputStream in, OutputStream out) {
|
||||
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(256, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buffer = new byte[NETWORK_BUFFER_SIZE];
|
||||
String from = i2ps.getThisDestination().calculateHash().toBase64().substring(0,6);
|
||||
String to = i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(direction + ": Forwarding between "
|
||||
+ from + " and " + to);
|
||||
}
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte[] buffer = ba.getData(); // new byte[NETWORK_BUFFER_SIZE];
|
||||
try {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
|
||||
if (len > 0) updateActivity();
|
||||
|
||||
if (in.available() == 0) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Flushing after sending " + len + " bytes through");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": " + len + " bytes flushed through to "
|
||||
+ i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6));
|
||||
try {
|
||||
Thread.sleep(I2PTunnel.PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (in.available() == 0) {
|
||||
out.flush(); // make sure the data get though
|
||||
|
||||
if (in.available() <= 0)
|
||||
out.flush(); // make sure the data get though
|
||||
}
|
||||
}
|
||||
//out.flush(); // close() flushes
|
||||
} catch (SocketException ex) {
|
||||
// this *will* occur when the other threads closes the socket
|
||||
synchronized (finishLock) {
|
||||
if (!finished) {
|
||||
_log.debug("Socket closed - error reading and writing",
|
||||
ex);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": Socket closed - error reading and writing",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedIOException ex) {
|
||||
_log.warn("Closing connection due to timeout (error: \""
|
||||
+ ex.getMessage() + "\")");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Closing connection due to timeout (error: \""
|
||||
+ ex.getMessage() + "\")");
|
||||
} catch (IOException ex) {
|
||||
if (!finished)
|
||||
_log.error("Error forwarding", ex);
|
||||
if (!finished) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error forwarding", ex);
|
||||
}
|
||||
//else
|
||||
// _log.warn("You may ignore this", ex);
|
||||
} finally {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info(direction + ": done forwarding between "
|
||||
+ from + " and " + to);
|
||||
}
|
||||
try {
|
||||
out.close();
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing streams", ex);
|
||||
_log.warn(direction + ": Error closing input stream", ex);
|
||||
}
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error flushing to close", ioe);
|
||||
}
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
finishLock.notifyAll();
|
||||
// the main thread will close sockets etc. now
|
||||
}
|
||||
_cache.release(ba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,19 +31,20 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
private final static Log _log = new Log(I2PTunnelServer.class);
|
||||
|
||||
private I2PSocketManager sockMgr;
|
||||
private I2PServerSocket i2pss;
|
||||
protected I2PSocketManager sockMgr;
|
||||
protected I2PServerSocket i2pss;
|
||||
|
||||
private Object lock = new Object(), slock = new Object();
|
||||
private Object lock = new Object();
|
||||
protected Object slock = new Object();
|
||||
|
||||
private InetAddress remoteHost;
|
||||
private int remotePort;
|
||||
protected InetAddress remoteHost;
|
||||
protected int remotePort;
|
||||
|
||||
private Logging l;
|
||||
|
||||
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
private long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
|
||||
@ -179,7 +180,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
_handleSocket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null);
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null, null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
_handleSocket.close();
|
||||
|
@ -16,6 +16,7 @@ import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -31,6 +32,7 @@ public class TunnelController implements Logging {
|
||||
private List _messages;
|
||||
private List _sessions;
|
||||
private boolean _running;
|
||||
private boolean _starting;
|
||||
|
||||
/**
|
||||
* Create a new controller for a tunnel out of the specific config options.
|
||||
@ -56,10 +58,9 @@ public class TunnelController implements Logging {
|
||||
setConfig(config, prefix);
|
||||
_messages = new ArrayList(4);
|
||||
_running = false;
|
||||
if (createKey && ("server".equals(getType())) )
|
||||
if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
|
||||
createPrivateKey();
|
||||
if (getStartOnLoad())
|
||||
startTunnel();
|
||||
_starting = getStartOnLoad();
|
||||
}
|
||||
|
||||
private void createPrivateKey() {
|
||||
@ -99,17 +100,24 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
public void startTunnelBackground() {
|
||||
if (_running) return;
|
||||
new I2PThread(new Runnable() { public void run() { startTunnel(); } }).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up the tunnel (if it isn't already running)
|
||||
*
|
||||
*/
|
||||
public void startTunnel() {
|
||||
_starting = true;
|
||||
try {
|
||||
doStartTunnel();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting up the tunnel", e);
|
||||
log("Error starting up the tunnel - " + e.getMessage());
|
||||
}
|
||||
_starting = false;
|
||||
}
|
||||
private void doStartTunnel() {
|
||||
if (_running) {
|
||||
@ -130,6 +138,8 @@ public class TunnelController implements Logging {
|
||||
startClient();
|
||||
} else if ("server".equals(type)) {
|
||||
startServer();
|
||||
} else if ("httpserver".equals(type)) {
|
||||
startHttpServer();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot start tunnel - unknown type [" + type + "]");
|
||||
@ -204,6 +214,18 @@ public class TunnelController implements Logging {
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startHttpServer() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
String targetHost = getTargetHost();
|
||||
String targetPort = getTargetPort();
|
||||
String spoofedHost = getSpoofedHost();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void setListenOn() {
|
||||
String listenOn = getListenOnInterface();
|
||||
if ( (listenOn != null) && (listenOn.length() > 0) ) {
|
||||
@ -231,6 +253,9 @@ public class TunnelController implements Logging {
|
||||
String host = getI2CPHost();
|
||||
if ( (host != null) && (host.length() > 0) )
|
||||
_tunnel.host = host;
|
||||
// woohah, special casing for people with ipv6/etc
|
||||
if ("localhost".equals(_tunnel.host))
|
||||
_tunnel.host = "127.0.0.1";
|
||||
String port = getI2CPPort();
|
||||
if ( (port != null) && (port.length() > 0) )
|
||||
_tunnel.port = port;
|
||||
@ -292,6 +317,7 @@ public class TunnelController implements Logging {
|
||||
public String getListenOnInterface() { return _config.getProperty("interface"); }
|
||||
public String getTargetHost() { return _config.getProperty("targetHost"); }
|
||||
public String getTargetPort() { return _config.getProperty("targetPort"); }
|
||||
public String getSpoofedHost() { return _config.getProperty("spoofedHost"); }
|
||||
public String getPrivKeyFile() { return _config.getProperty("privKeyFile"); }
|
||||
public String getListenPort() { return _config.getProperty("listenPort"); }
|
||||
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
|
||||
@ -299,6 +325,7 @@ public class TunnelController implements Logging {
|
||||
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
|
||||
|
||||
public boolean getIsRunning() { return _running; }
|
||||
public boolean getIsStarting() { return _starting; }
|
||||
|
||||
public void getSummary(StringBuffer buf) {
|
||||
String type = getType();
|
||||
@ -308,6 +335,8 @@ public class TunnelController implements Logging {
|
||||
getClientSummary(buf);
|
||||
else if ("server".equals(type))
|
||||
getServerSummary(buf);
|
||||
else if ("httpserver".equals(type))
|
||||
getHttpServerSummary(buf);
|
||||
else
|
||||
buf.append("Unknown type ").append(type);
|
||||
}
|
||||
@ -361,6 +390,18 @@ public class TunnelController implements Logging {
|
||||
getOptionSummary(buf);
|
||||
}
|
||||
|
||||
private void getHttpServerSummary(StringBuffer buf) {
|
||||
String description = getDescription();
|
||||
if ( (description != null) && (description.trim().length() > 0) )
|
||||
buf.append("<i>").append(description).append("</i><br />\n");
|
||||
buf.append("Server tunnel pointing at port ").append(getTargetPort());
|
||||
buf.append(" on ").append(getTargetHost());
|
||||
buf.append(" for the site ").append(getSpoofedHost());
|
||||
buf.append("<br />\n");
|
||||
buf.append("Private destination loaded from ").append(getPrivKeyFile()).append("<br />\n");
|
||||
getOptionSummary(buf);
|
||||
}
|
||||
|
||||
private void getOptionSummary(StringBuffer buf) {
|
||||
String opts = getClientOptions();
|
||||
if ( (opts != null) && (opts.length() > 0) )
|
||||
@ -372,7 +413,7 @@ public class TunnelController implements Logging {
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null) {
|
||||
buf.append("Destination hash: ").append(dest.calculateHash().toBase64()).append("<br />\n");
|
||||
if ("server".equals(getType())) {
|
||||
if ( ("server".equals(getType())) || ("httpserver".equals(getType())) ) {
|
||||
buf.append("Full destination: ");
|
||||
buf.append("<input type=\"text\" size=\"10\" onclick=\"this.select();\" ");
|
||||
buf.append("value=\"").append(dest.toBase64()).append("\" />\n");
|
||||
|
@ -8,6 +8,7 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@ -20,6 +21,8 @@ import java.util.TreeMap;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -43,28 +46,34 @@ public class TunnelControllerGroup {
|
||||
*/
|
||||
private Map _sessions;
|
||||
|
||||
public static TunnelControllerGroup getInstance() { return _instance; }
|
||||
public static TunnelControllerGroup getInstance() {
|
||||
synchronized (TunnelControllerGroup.class) {
|
||||
if (_instance == null)
|
||||
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private TunnelControllerGroup(String configFile) {
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelControllerGroup.class);
|
||||
_instance = this;
|
||||
_controllers = new ArrayList();
|
||||
_controllers = Collections.synchronizedList(new ArrayList());
|
||||
_configFile = configFile;
|
||||
_sessions = new HashMap(4);
|
||||
loadControllers(_configFile);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if ( (args == null) || (args.length <= 0) ) {
|
||||
new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
} else if (args.length == 1) {
|
||||
if (DEFAULT_CONFIG_FILE.equals(args[0]))
|
||||
new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
else
|
||||
new TunnelControllerGroup(args[0]);
|
||||
} else {
|
||||
System.err.println("Usage: TunnelControllerGroup [filename]");
|
||||
return;
|
||||
synchronized (TunnelControllerGroup.class) {
|
||||
if (_instance != null) return; // already loaded through the web
|
||||
|
||||
if ( (args == null) || (args.length <= 0) ) {
|
||||
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
} else if (args.length == 1) {
|
||||
_instance = new TunnelControllerGroup(args[0]);
|
||||
} else {
|
||||
System.err.println("Usage: TunnelControllerGroup [filename]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,10 +98,24 @@ public class TunnelControllerGroup {
|
||||
_controllers.add(controller);
|
||||
i++;
|
||||
}
|
||||
I2PThread startupThread = new I2PThread(new StartControllers(), "Startup tunnels");
|
||||
startupThread.start();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(i + " controllers loaded from " + configFile);
|
||||
}
|
||||
|
||||
private class StartControllers implements Runnable {
|
||||
public void run() {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
if (controller.getStartOnLoad())
|
||||
controller.startTunnel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void reloadControllers() {
|
||||
unloadControllers();
|
||||
loadControllers(_configFile);
|
||||
@ -143,7 +166,6 @@ public class TunnelControllerGroup {
|
||||
controller.stopTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers stopped");
|
||||
return msgs;
|
||||
@ -161,7 +183,7 @@ public class TunnelControllerGroup {
|
||||
controller.startTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers started");
|
||||
return msgs;
|
||||
@ -259,33 +281,13 @@ public class TunnelControllerGroup {
|
||||
}
|
||||
|
||||
Properties props = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(cfgFile);
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(fis));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.length() <= 0) continue;
|
||||
if (line.startsWith("#") || line.startsWith(";"))
|
||||
continue;
|
||||
int eq = line.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= line.length() - 1) )
|
||||
continue;
|
||||
String key = line.substring(0, eq);
|
||||
String val = line.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Props loaded with " + props.size() + " lines");
|
||||
DataHelper.loadProps(props, cfgFile);
|
||||
return props;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error reading the controllers from " + configFile, ioe);
|
||||
return null;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ class WebEditPageFormGenerator {
|
||||
"<option value=\"httpclient\">HTTP proxy</option>" +
|
||||
"<option value=\"client\">Client tunnel</option>" +
|
||||
"<option value=\"server\">Server tunnel</option>" +
|
||||
"<option value=\"httpserver\">HTTP server tunnel</option>" +
|
||||
"</select> <input type=\"submit\" value=\"GO\" />" +
|
||||
"</form>\n";
|
||||
|
||||
@ -42,6 +43,8 @@ class WebEditPageFormGenerator {
|
||||
return getEditClientForm(controller, id);
|
||||
else if ("server".equals(type))
|
||||
return getEditServerForm(controller, id);
|
||||
else if ("httpserver".equals(type))
|
||||
return getEditHttpServerForm(controller, id);
|
||||
else
|
||||
return "WTF, unknown type [" + type + "]";
|
||||
}
|
||||
@ -60,6 +63,9 @@ class WebEditPageFormGenerator {
|
||||
buf.append("value=\"squid.i2p\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
buf.append("<hr />Note: the following options are shared across all client tunnels and");
|
||||
buf.append(" HTTP proxies<br />\n");
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
@ -80,6 +86,9 @@ class WebEditPageFormGenerator {
|
||||
buf.append("value=\"").append(controller.getTargetDestination()).append("\" ");
|
||||
buf.append(" /> (either the hosts.txt name or the full base64 destination)<br />\n");
|
||||
|
||||
buf.append("<hr />Note: the following options are shared across all client tunnels and");
|
||||
buf.append(" HTTP proxies<br />\n");
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\"><br />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
@ -97,7 +106,7 @@ class WebEditPageFormGenerator {
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"localhost\" ");
|
||||
buf.append("value=\"127.0.0.1\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
@ -123,6 +132,48 @@ class WebEditPageFormGenerator {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditHttpServerForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>HTTP server tunnel</i><input type=\"hidden\" name=\"type\" value=\"httpserver\" /><br />\n");
|
||||
|
||||
buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"127.0.0.1\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
if ( (controller != null) && (controller.getTargetPort() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"80\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Website hostname:</b> <input type=\"text\" size=\"16\" name=\"spoofedHost\" ");
|
||||
if ( (controller != null) && (controller.getSpoofedHost() != null) )
|
||||
buf.append("value=\"").append(controller.getSpoofedHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"mysite.i2p\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
|
||||
if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
|
||||
buf.append(controller.getPrivKeyFile()).append("\" /><br />");
|
||||
} else {
|
||||
buf.append("myServer.privKey\" /><br />");
|
||||
buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
|
||||
}
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start off the form and add some common fields (name, num, description)
|
||||
*
|
||||
@ -145,6 +196,13 @@ class WebEditPageFormGenerator {
|
||||
if ( (controller != null) && (controller.getDescription() != null) )
|
||||
buf.append("value=\"").append(controller.getDescription()).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
buf.append("<b>Start automatically?</b> \n");
|
||||
buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
|
||||
if ( (controller != null) && (controller.getStartOnLoad()) )
|
||||
buf.append(" checked=\"true\" />\n<br />\n");
|
||||
else
|
||||
buf.append(" />\n<br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,6 +252,7 @@ class WebEditPageFormGenerator {
|
||||
private static void addOptions(StringBuffer buf, TunnelController controller) {
|
||||
int tunnelDepth = 2;
|
||||
int numTunnels = 2;
|
||||
int connectDelay = 0;
|
||||
Properties opts = getOptions(controller);
|
||||
if (opts != null) {
|
||||
String depth = opts.getProperty("tunnels.depthInbound");
|
||||
@ -212,6 +271,14 @@ class WebEditPageFormGenerator {
|
||||
numTunnels = 2;
|
||||
}
|
||||
}
|
||||
String delay = opts.getProperty("i2p.streaming.connectDelay");
|
||||
if (delay != null) {
|
||||
try {
|
||||
connectDelay = Integer.parseInt(delay);
|
||||
} catch (NumberFormatException nfe) {
|
||||
connectDelay = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.append("<b>Tunnel depth:</b> ");
|
||||
@ -251,12 +318,19 @@ class WebEditPageFormGenerator {
|
||||
}
|
||||
buf.append("</select><br />\n");
|
||||
|
||||
buf.append("<b>Delay connection briefly? </b> ");
|
||||
buf.append("<input type=\"checkbox\" name=\"connectDelay\" value=\"");
|
||||
buf.append((connectDelay > 0 ? connectDelay : 1000)).append("\" ");
|
||||
if (connectDelay > 0)
|
||||
buf.append("checked=\"true\" ");
|
||||
buf.append("/> (useful for brief request/response connections)<br />\n");
|
||||
|
||||
buf.append("<b>I2CP host:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientHost\" size=\"20\" value=\"");
|
||||
if ( (controller != null) && (controller.getI2CPHost() != null) )
|
||||
buf.append(controller.getI2CPHost());
|
||||
else
|
||||
buf.append("localhost");
|
||||
buf.append("127.0.0.1");
|
||||
buf.append("\" /><br />\n");
|
||||
buf.append("<b>I2CP port:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");
|
||||
@ -275,18 +349,13 @@ class WebEditPageFormGenerator {
|
||||
String val = opts.getProperty(key);
|
||||
if ("tunnels.depthInbound".equals(key)) continue;
|
||||
if ("tunnels.numInbound".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
buf.append("\" /><br />\n");
|
||||
buf.append("<b>Start automatically?</b> \n");
|
||||
buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
|
||||
if ( (controller != null) && (controller.getStartOnLoad()) )
|
||||
buf.append(" checked=\"true\" />\n<br />\n");
|
||||
else
|
||||
buf.append(" />\n<br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,6 +29,7 @@ public class WebEditPageHelper {
|
||||
private String _i2cpPort;
|
||||
private String _tunnelDepth;
|
||||
private String _tunnelCount;
|
||||
private boolean _connectDelay;
|
||||
private String _customOptions;
|
||||
private String _proxyList;
|
||||
private String _port;
|
||||
@ -37,6 +38,7 @@ public class WebEditPageHelper {
|
||||
private String _targetDestination;
|
||||
private String _targetHost;
|
||||
private String _targetPort;
|
||||
private String _spoofedHost;
|
||||
private String _privKeyFile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _privKeyGenerate;
|
||||
@ -138,6 +140,10 @@ public class WebEditPageHelper {
|
||||
public void setTargetPort(String port) {
|
||||
_targetPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** What host does this http server tunnel spoof */
|
||||
public void setSpoofedHost(String host) {
|
||||
_spoofedHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What filename is this server tunnel's private keys stored in */
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
@ -164,6 +170,9 @@ public class WebEditPageHelper {
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
public void setConnectDelay(String moo) {
|
||||
_connectDelay = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the form and display any resulting messages
|
||||
@ -248,10 +257,33 @@ public class WebEditPageHelper {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", _privKeyGenerate);
|
||||
TunnelControllerGroup.getInstance().addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = TunnelControllerGroup.getInstance().getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null)
|
||||
cOpt.setProperty("option.tunnels.numInbound", _tunnelCount);
|
||||
if (_tunnelDepth != null)
|
||||
cOpt.setProperty("option.tunnels.depthInbound", _tunnelDepth);
|
||||
if (_connectDelay)
|
||||
cOpt.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
cOpt.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
c.setConfig(cOpt, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getMessages(doSave());
|
||||
}
|
||||
@ -295,6 +327,15 @@ public class WebEditPageHelper {
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -324,6 +365,7 @@ public class WebEditPageHelper {
|
||||
String val = pair.substring(eq+1);
|
||||
if ("tunnels.numInbound".equals(key)) continue;
|
||||
if ("tunnels.depthInbound".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
config.setProperty("option." + key, val);
|
||||
}
|
||||
}
|
||||
@ -334,6 +376,10 @@ public class WebEditPageHelper {
|
||||
config.setProperty("option.tunnels.numInbound", _tunnelCount);
|
||||
if (_tunnelDepth != null)
|
||||
config.setProperty("option.tunnels.depthInbound", _tunnelDepth);
|
||||
if (_connectDelay)
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,6 +68,8 @@ public class WebStatusPageHelper {
|
||||
if (controller.getIsRunning()) {
|
||||
buf.append("<i>running</i> ");
|
||||
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=stop\">stop</a> ");
|
||||
} else if (controller.getIsStarting()) {
|
||||
buf.append("<i>startup in progress (please be patient)</i>");
|
||||
} else {
|
||||
buf.append("<i>not running</i> ");
|
||||
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=start\">start</a> ");
|
||||
|
@ -47,7 +47,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket();
|
||||
new I2PTunnelRunner(clientSock, destSock, sockLock, null);
|
||||
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
|
||||
} catch (SOCKSException e) {
|
||||
_log.error("Error from SOCKS connection: " + e.getMessage());
|
||||
closeSocket(s);
|
||||
|
@ -81,7 +81,7 @@ public abstract class SOCKSServer {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_log.debug("connecting to " + connHostName + "...");
|
||||
I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
destSock = sm.connect(I2PTunnel.destFromName(connHostName), new I2PSocketOptions());
|
||||
destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} else {
|
||||
|
@ -25,6 +25,7 @@
|
||||
<option value="httpclient">HTTP proxy</option>
|
||||
<option value="client">Client tunnel</option>
|
||||
<option value="server">Server tunnel</option>
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="GO" />
|
||||
</form>
|
||||
|
||||
|
@ -5,7 +5,7 @@ package net.i2p.client.streaming;
|
||||
* so care should be taken when using in a multithreaded environment.
|
||||
*
|
||||
*/
|
||||
public class ByteCollector {
|
||||
class ByteCollector {
|
||||
byte[] contents;
|
||||
int size;
|
||||
|
||||
|
@ -24,7 +24,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
public static final int MAX_PACKET_SIZE = 1024 * 32;
|
||||
public static final int PACKET_DELAY = 100;
|
||||
|
||||
private I2PSocketManager manager;
|
||||
private I2PSocketManagerImpl manager;
|
||||
private Destination local;
|
||||
private Destination remote;
|
||||
private String localID;
|
||||
@ -69,7 +69,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
|
||||
* @param localID what is our half of the socket ID?
|
||||
*/
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManagerImpl mgr, boolean outgoing, String localID) {
|
||||
this.outgoing = outgoing;
|
||||
manager = mgr;
|
||||
remote = peer;
|
||||
@ -157,7 +157,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: RemoteID set to "
|
||||
+ I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ I2PSocketManagerImpl.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
}
|
||||
return remoteID;
|
||||
@ -249,9 +249,9 @@ class I2PSocketImpl implements I2PSocket {
|
||||
|
||||
private byte getMask(int add) {
|
||||
if (outgoing)
|
||||
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
|
||||
return (byte)(I2PSocketManagerImpl.DATA_IN + (byte)add);
|
||||
else
|
||||
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
|
||||
return (byte)(I2PSocketManagerImpl.DATA_OUT + (byte)add);
|
||||
}
|
||||
|
||||
public void setOptions(I2PSocketOptions options) {
|
||||
@ -614,7 +614,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
_log.info(getPrefix() + ":" + Thread.currentThread().getName()
|
||||
+ "Sending close packet: (we started? " + outgoing
|
||||
+ ") after reading " + _bytesRead + " and writing " + _bytesWritten);
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
boolean sent = manager.getSession().sendMessage(remote, packet);
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -645,7 +645,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
_log.error(getPrefix() + "NULL REMOTEID");
|
||||
return false;
|
||||
}
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x00), remoteID, data);
|
||||
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x00), remoteID, data);
|
||||
boolean sent;
|
||||
synchronized (flagLock) {
|
||||
if (closed2) return false;
|
||||
|
@ -13,6 +13,7 @@ import java.net.NoRouteToHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@ -34,62 +35,8 @@ import net.i2p.util.Log;
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManager implements I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket = null;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManager() {
|
||||
this("SocketManager " + (++__managerId));
|
||||
}
|
||||
public I2PSocketManager(String name) {
|
||||
_name = name;
|
||||
_session = null;
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(I2PSocketManager.class);
|
||||
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
public interface I2PSocketManager {
|
||||
public I2PSession getSession();
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
@ -97,343 +44,14 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.info(getName() + ": Disconnected from the session");
|
||||
destroySocketManager();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error(getName() + ": Error occurred: [" + message + "]", error);
|
||||
}
|
||||
public void setAcceptTimeout(long ms);
|
||||
public long getAcceptTimeout();
|
||||
public void setDefaultOptions(I2PSocketOptions options);
|
||||
public I2PSocketOptions getDefaultOptions();
|
||||
public I2PServerSocket getServerSocket();
|
||||
|
||||
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(getName() + ": Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length < 4) {
|
||||
_log.warn(getName() + ": ==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.warn(getName() + ": Error processing", ise);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug(getName() + ": Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
long begin = _context.clock().now();
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn(getName() + ": No socket responsible for ACK packet for id " + getReadableForm(id));
|
||||
return;
|
||||
}
|
||||
|
||||
long socketRetrieved = _context.clock().now();
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
long beforeSetRemId = _context.clock().now();
|
||||
s.setRemoteID(newID);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": ackAvailable - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, getRemoteId took "
|
||||
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
|
||||
+ (_context.clock().now()-beforeSetRemId) + "ms");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": invalid ack - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, overall took "
|
||||
+ (_context.clock().now()-begin) + "ms");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect outgoing for socket " + s + " on id "
|
||||
+ getReadableForm(id));
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug(getName() + ": Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect for socket " + s, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket "
|
||||
+ s + " on id " + getReadableForm(id));
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
_log.debug(getName() + ": *Syn! for socket " + s + " on id " + getReadableForm(newLocalID)
|
||||
+ " from " + d.calculateHash().toBase64().substring(0,6));
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.warn(getName() + ": Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
_context.statManager().addRateData("streaming.nackSent", 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message for socket " + s,
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming,nackSent", 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.warn(getName() + ": Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error(getName() + ": Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = options;
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
if (_serverSocket == null) {
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
}
|
||||
return _serverSocket;
|
||||
}
|
||||
public I2PSocketOptions buildOptions();
|
||||
public I2PSocketOptions buildOptions(Properties opts);
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
@ -448,117 +66,7 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": connect(" + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ", ...): localID = " + lcID);
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket "
|
||||
+ s + " with localID = " + lcID);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": syn sent ok to "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " with localID = " + lcID);
|
||||
}
|
||||
if (options != null)
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
else
|
||||
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": remoteID received from "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ": " + getReadableForm(remoteID)
|
||||
+ " with localID = " + lcID);
|
||||
|
||||
if (remoteID == null) {
|
||||
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
|
||||
throw new ConnectException("Connection refused by peer for socket " + s);
|
||||
}
|
||||
if ("".equals(remoteID)) {
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID) + " for socket " + s);
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Timeout waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Connection error waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": No route to host waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.warn(getName() + ": Unhandled error connecting on "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
NoRouteToHostException, InterruptedIOException;
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
@ -571,182 +79,37 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
NoRouteToHostException, InterruptedIOException;
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
Iterator iter;
|
||||
String id = null;
|
||||
I2PSocketImpl sock;
|
||||
|
||||
iter = _inSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
iter = _outSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": Waiting for all open sockets to really close...");
|
||||
synchronized (lock) {
|
||||
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_log.debug(getName() + ": Destroying I2P session...");
|
||||
_session.destroySession();
|
||||
_log.debug(getName() + ": I2P session destroyed");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.warn(getName() + ": Error destroying I2P session", e);
|
||||
}
|
||||
}
|
||||
public void destroySocketManager();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
public Set listSockets();
|
||||
|
||||
/**
|
||||
* 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) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.warn(getName() + ": I2PException:", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean ping(Destination peer, long timeoutMs);
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
String localId = sock.getLocalID();
|
||||
boolean removed = false;
|
||||
synchronized (lock) {
|
||||
removed = (null != _inSockets.remove(localId));
|
||||
removed = removed || (null != _outSockets.remove(localId));
|
||||
lock.notify();
|
||||
}
|
||||
public String getName();
|
||||
public void setName(String name);
|
||||
|
||||
long now = _context.clock().now();
|
||||
long lifetime = now - sock.getCreatedOn();
|
||||
long timeSinceClose = now - sock.getClosedOn();
|
||||
long sent = sock.getBytesSent();
|
||||
long recv = sock.getBytesReceived();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": Removing socket \"" + getReadableForm(localId) + "\" [" + sock
|
||||
+ ", send: " + sent + ", recv: " + recv
|
||||
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose
|
||||
+ " removed? " + removed + ")]",
|
||||
new Exception("removeSocket called"));
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
|
||||
_context.statManager().addRateData("streaming.sent", sent, lifetime);
|
||||
_context.statManager().addRateData("streaming.received", recv, lifetime);
|
||||
|
||||
if (sent > recv) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
|
||||
} else if (recv > sent) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!
|
||||
*/
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
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 = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
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;
|
||||
}
|
||||
public void addDisconnectListener(DisconnectListener lsnr);
|
||||
public void removeDisconnectListener(DisconnectListener lsnr);
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
public static interface DisconnectListener {
|
||||
public void sessionDisconnected();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
@ -23,6 +25,10 @@ import net.i2p.util.Log;
|
||||
public class I2PSocketManagerFactory {
|
||||
private final static Log _log = new Log(I2PSocketManagerFactory.class);
|
||||
|
||||
public static final String PROP_MANAGER = "i2p.streaming.manager";
|
||||
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
|
||||
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
@ -76,23 +82,76 @@ public class I2PSocketManagerFactory {
|
||||
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);
|
||||
if (opts == null)
|
||||
opts = new Properties();
|
||||
for (Iterator iter = System.getProperties().keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (!opts.containsKey(name))
|
||||
opts.setProperty(name, System.getProperty(name));
|
||||
}
|
||||
boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER));
|
||||
if (oldLib && false) {
|
||||
// for the old streaming lib
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
//opts.setProperty("tunnels.depthInbound", "0");
|
||||
} else {
|
||||
// for new streaming lib:
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
|
||||
//p.setProperty("tunnels.depthInbound", "0");
|
||||
}
|
||||
|
||||
if (i2cpHost != null)
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
if (i2cpPort > 0)
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
|
||||
try {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
session.connect();
|
||||
return createManager(session);
|
||||
I2PSocketManager sockMgr = createManager(session, opts, "manager");
|
||||
if (sockMgr != null)
|
||||
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
|
||||
return sockMgr;
|
||||
} 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);
|
||||
mgr.setDefaultOptions(new I2PSocketOptions());
|
||||
return mgr;
|
||||
private static I2PSocketManager createManager(I2PSession session, Properties opts, String name) {
|
||||
if (false) {
|
||||
I2PSocketManagerImpl mgr = new I2PSocketManagerImpl();
|
||||
mgr.setSession(session);
|
||||
//mgr.setDefaultOptions(new I2PSocketOptions());
|
||||
return mgr;
|
||||
} else {
|
||||
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);
|
||||
if (classname != null) {
|
||||
try {
|
||||
Class cls = Class.forName(classname);
|
||||
Object obj = cls.newInstance();
|
||||
if (obj instanceof I2PSocketManager) {
|
||||
I2PSocketManager mgr = (I2PSocketManager)obj;
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
mgr.init(context, session, opts, name);
|
||||
return mgr;
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "]");
|
||||
}
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
_log.error("Error loading " + classname, cnfe);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - not found");
|
||||
} catch (InstantiationException ie) {
|
||||
_log.error("Error loading " + classname, ie);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - unable to instantiate");
|
||||
} catch (IllegalAccessException iae) {
|
||||
_log.error("Error loading " + classname, iae);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - illegal access");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("No manager class specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,787 @@
|
||||
/*
|
||||
* licensed under BSD license...
|
||||
* (if you know the proper clause for that, add it ...)
|
||||
*/
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
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
|
||||
*
|
||||
*/
|
||||
class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket = null;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private List _listeners;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManagerImpl() {
|
||||
this("SocketManager " + (++__managerId));
|
||||
}
|
||||
public I2PSocketManagerImpl(String name) {
|
||||
init(I2PAppContext.getGlobalContext(), null, null, name);
|
||||
}
|
||||
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
|
||||
_name = name;
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(I2PSocketManager.class);
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
_listeners = new ArrayList(1);
|
||||
setSession(session);
|
||||
setDefaultOptions(buildOptions(opts));
|
||||
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
* sending back a NACK/Close?
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.info(getName() + ": Disconnected from the session");
|
||||
destroySocketManager();
|
||||
List listeners = null;
|
||||
synchronized (_listeners) {
|
||||
listeners = new ArrayList(_listeners);
|
||||
_listeners.clear();
|
||||
}
|
||||
for (int i = 0; i < listeners.size(); i++) {
|
||||
DisconnectListener lsnr = (DisconnectListener)listeners.get(i);
|
||||
lsnr.sessionDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error(getName() + ": 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(getName() + ": Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length < 4) {
|
||||
_log.warn(getName() + ": ==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.warn(getName() + ": Error processing", ise);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug(getName() + ": Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
long begin = _context.clock().now();
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn(getName() + ": No socket responsible for ACK packet for id " + getReadableForm(id));
|
||||
return;
|
||||
}
|
||||
|
||||
long socketRetrieved = _context.clock().now();
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
long beforeSetRemId = _context.clock().now();
|
||||
s.setRemoteID(newID);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": ackAvailable - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, getRemoteId took "
|
||||
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
|
||||
+ (_context.clock().now()-beforeSetRemId) + "ms");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": invalid ack - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, overall took "
|
||||
+ (_context.clock().now()-begin) + "ms");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect outgoing for socket " + s + " on id "
|
||||
+ getReadableForm(id));
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug(getName() + ": Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect for socket " + s, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket "
|
||||
+ s + " on id " + getReadableForm(id));
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
_log.debug(getName() + ": *Syn! for socket " + s + " on id " + getReadableForm(newLocalID)
|
||||
+ " from " + d.calculateHash().toBase64().substring(0,6));
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.warn(getName() + ": Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
_context.statManager().addRateData("streaming.nackSent", 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message for socket " + s,
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming,nackSent", 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.warn(getName() + ": Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error(getName() + ": Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = options;
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PSocketOptions buildOptions() { return buildOptions(null); }
|
||||
public I2PSocketOptions buildOptions(Properties opts) {
|
||||
return new I2PSocketOptionsImpl(opts);
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
if (_serverSocket == null) {
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
}
|
||||
return _serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
* @param options I2P socket options to be used for connecting
|
||||
*
|
||||
* @throws ConnectException if the peer refuses the connection
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": connect(" + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ", ...): localID = " + lcID);
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket "
|
||||
+ s + " with localID = " + lcID);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": syn sent ok to "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " with localID = " + lcID);
|
||||
}
|
||||
if (options != null)
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
else
|
||||
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": remoteID received from "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ": " + getReadableForm(remoteID)
|
||||
+ " with localID = " + lcID);
|
||||
|
||||
if (remoteID == null) {
|
||||
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
|
||||
throw new ConnectException("Connection refused by peer for socket " + s);
|
||||
}
|
||||
if ("".equals(remoteID)) {
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID) + " for socket " + s);
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Timeout waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Connection error waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": No route to host waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.warn(getName() + ": Unhandled error connecting on "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
*
|
||||
* @throws ConnectException if the peer refuses the connection
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
Iterator iter;
|
||||
String id = null;
|
||||
I2PSocketImpl sock;
|
||||
|
||||
iter = _inSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
iter = _outSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": Waiting for all open sockets to really close...");
|
||||
synchronized (lock) {
|
||||
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_log.debug(getName() + ": Destroying I2P session...");
|
||||
_session.destroySession();
|
||||
_log.debug(getName() + ": I2P session destroyed");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.warn(getName() + ": Error destroying I2P session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.warn(getName() + ": I2PException:", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
String localId = sock.getLocalID();
|
||||
boolean removed = false;
|
||||
synchronized (lock) {
|
||||
removed = (null != _inSockets.remove(localId));
|
||||
removed = removed || (null != _outSockets.remove(localId));
|
||||
lock.notify();
|
||||
}
|
||||
|
||||
long now = _context.clock().now();
|
||||
long lifetime = now - sock.getCreatedOn();
|
||||
long timeSinceClose = now - sock.getClosedOn();
|
||||
long sent = sock.getBytesSent();
|
||||
long recv = sock.getBytesReceived();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": Removing socket \"" + getReadableForm(localId) + "\" [" + sock
|
||||
+ ", send: " + sent + ", recv: " + recv
|
||||
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose
|
||||
+ " removed? " + removed + ")]",
|
||||
new Exception("removeSocket called"));
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
|
||||
_context.statManager().addRateData("streaming.sent", sent, lifetime);
|
||||
_context.statManager().addRateData("streaming.received", recv, lifetime);
|
||||
|
||||
if (sent > recv) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
|
||||
} else if (recv > sent) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
|
||||
public void addDisconnectListener(DisconnectListener lsnr) {
|
||||
synchronized (_listeners) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
}
|
||||
public void removeDisconnectListener(DisconnectListener lsnr) {
|
||||
synchronized (_listeners) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!
|
||||
*/
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
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 = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
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;
|
||||
}
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +1,43 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
*
|
||||
*/
|
||||
public class I2PSocketOptions {
|
||||
private long _connectTimeout;
|
||||
private long _readTimeout;
|
||||
private long _writeTimeout;
|
||||
private int _maxBufferSize;
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
|
||||
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
|
||||
public interface I2PSocketOptions {
|
||||
public static final String PROP_BUFFER_SIZE = "i2p.streaming.bufferSize";
|
||||
public static final String PROP_CONNECT_TIMEOUT = "i2p.streaming.connectTimeout";
|
||||
public static final String PROP_READ_TIMEOUT = "i2p.streaming.readTimeout";
|
||||
public static final String PROP_WRITE_TIMEOUT = "i2p.streaming.writeTimeout";
|
||||
|
||||
public I2PSocketOptions() {
|
||||
_connectTimeout = -1;
|
||||
_readTimeout = -1;
|
||||
_writeTimeout = DEFAULT_WRITE_TIMEOUT;
|
||||
_maxBufferSize = DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 long getConnectTimeout();
|
||||
|
||||
/**
|
||||
* Define how long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
*/
|
||||
public void setConnectTimeout(long ms) {
|
||||
_connectTimeout = ms;
|
||||
}
|
||||
public void setConnectTimeout(long ms);
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return _readTimeout;
|
||||
}
|
||||
public long getReadTimeout();
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public void setReadTimeout(long ms) {
|
||||
_readTimeout = ms;
|
||||
}
|
||||
public void setReadTimeout(long ms);
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
@ -63,9 +47,7 @@ public class I2PSocketOptions {
|
||||
*
|
||||
* @return buffer size limit, in bytes
|
||||
*/
|
||||
public int getMaxBufferSize() {
|
||||
return _maxBufferSize;
|
||||
}
|
||||
public int getMaxBufferSize();
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
@ -74,9 +56,7 @@ public class I2PSocketOptions {
|
||||
* less than or equal to zero, there is no limit (warning: can eat ram)
|
||||
*
|
||||
*/
|
||||
public void setMaxBufferSize(int numBytes) {
|
||||
_maxBufferSize = numBytes;
|
||||
}
|
||||
public void setMaxBufferSize(int numBytes);
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
@ -84,9 +64,7 @@ public class I2PSocketOptions {
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public long getWriteTimeout() {
|
||||
return _writeTimeout;
|
||||
}
|
||||
public long getWriteTimeout();
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
@ -94,7 +72,5 @@ public class I2PSocketOptions {
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public void setWriteTimeout(long ms) {
|
||||
_writeTimeout = ms;
|
||||
}
|
||||
public void setWriteTimeout(long ms);
|
||||
}
|
||||
|
@ -0,0 +1,136 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
*
|
||||
*/
|
||||
class I2PSocketOptionsImpl implements I2PSocketOptions {
|
||||
private long _connectTimeout;
|
||||
private long _readTimeout;
|
||||
private long _writeTimeout;
|
||||
private int _maxBufferSize;
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
|
||||
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT = 60*1000;
|
||||
|
||||
public I2PSocketOptionsImpl() {
|
||||
this(System.getProperties());
|
||||
}
|
||||
|
||||
public I2PSocketOptionsImpl(I2PSocketOptions opts) {
|
||||
this(System.getProperties());
|
||||
if (opts != null) {
|
||||
_connectTimeout = opts.getConnectTimeout();
|
||||
_readTimeout = opts.getReadTimeout();
|
||||
_writeTimeout = opts.getWriteTimeout();
|
||||
_maxBufferSize = opts.getMaxBufferSize();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocketOptionsImpl(Properties opts) {
|
||||
init(opts);
|
||||
}
|
||||
|
||||
protected void init(Properties opts) {
|
||||
_maxBufferSize = getInt(opts, PROP_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
|
||||
_connectTimeout = getInt(opts, PROP_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
|
||||
_readTimeout = getInt(opts, PROP_READ_TIMEOUT, -1);
|
||||
_writeTimeout = getInt(opts, PROP_WRITE_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
|
||||
}
|
||||
|
||||
protected int getInt(Properties opts, String name, int defaultVal) {
|
||||
if (opts == null) return defaultVal;
|
||||
String val = opts.getProperty(name);
|
||||
if (val == null) {
|
||||
return defaultVal;
|
||||
} else {
|
||||
try {
|
||||
return Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define how long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
*/
|
||||
public void setConnectTimeout(long ms) {
|
||||
_connectTimeout = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return _readTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public void setReadTimeout(long ms) {
|
||||
_readTimeout = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
* this amount has been exceeded, subsequent .write calls will block until
|
||||
* either some data is removed or the connection is closed. If this is
|
||||
* less than or equal to zero, there is no limit (warning: can eat ram)
|
||||
*
|
||||
* @return buffer size limit, in bytes
|
||||
*/
|
||||
public int getMaxBufferSize() {
|
||||
return _maxBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
* this amount has been exceeded, subsequent .write calls will block until
|
||||
* either some data is removed or the connection is closed. If this is
|
||||
* less than or equal to zero, there is no limit (warning: can eat ram)
|
||||
*
|
||||
*/
|
||||
public void setMaxBufferSize(int numBytes) {
|
||||
_maxBufferSize = numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
* for the data to flush. If this value is exceeded, the write() throws
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public long getWriteTimeout() {
|
||||
return _writeTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
* for the data to flush. If this value is exceeded, the write() throws
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public void setWriteTimeout(long ms) {
|
||||
_writeTimeout = ms;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@ -26,6 +27,8 @@ public class StreamSinkClient {
|
||||
private int _sendSize;
|
||||
private int _writeDelay;
|
||||
private String _peerDestFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
|
||||
|
||||
/**
|
||||
@ -35,6 +38,11 @@ public class StreamSinkClient {
|
||||
* @param serverDestFile file containing the StreamSinkServer's binary Destination
|
||||
*/
|
||||
public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) {
|
||||
this(null, -1, sendSize, writeDelayMs, serverDestFile);
|
||||
}
|
||||
public StreamSinkClient(String i2cpHost, int i2cpPort, int sendSize, int writeDelayMs, String serverDestFile) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_sendSize = sendSize;
|
||||
_writeDelay = writeDelayMs;
|
||||
_peerDestFile = serverDestFile;
|
||||
@ -46,7 +54,11 @@ public class StreamSinkClient {
|
||||
*
|
||||
*/
|
||||
public void runClient() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
I2PSocketManager mgr = null;
|
||||
if (_i2cpHost != null)
|
||||
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination peer = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@ -81,9 +93,9 @@ public class StreamSinkClient {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
sock.close();
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
@ -103,7 +115,7 @@ public class StreamSinkClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile</code> <br />
|
||||
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendSizeKB</b>: how many KB to send</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
@ -111,25 +123,40 @@ public class StreamSinkClient {
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.out.println("Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile");
|
||||
} else {
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Send size invalid [" + args[0] + "]");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
StreamSinkClient client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
client.runClient();
|
||||
StreamSinkClient client = null;
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Send size invalid [" + args[0] + "]");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
break;
|
||||
case 5:
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
sendSizeKB = Integer.parseInt(args[2]);
|
||||
writeDelayMs = Integer.parseInt(args[3]);
|
||||
client = new StreamSinkClient(args[0], port, sendSizeKB, writeDelayMs, args[4]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("arg error");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile");
|
||||
}
|
||||
if (client != null)
|
||||
client.runClient();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple streaming lib test app that connects to a given destination and sends
|
||||
* the contents of a file, then disconnects. See the {@link #main}
|
||||
*
|
||||
*/
|
||||
public class StreamSinkSend {
|
||||
private Log _log;
|
||||
private String _sendFile;
|
||||
private int _writeDelay;
|
||||
private String _peerDestFile;
|
||||
|
||||
/**
|
||||
* Build the client but don't fire it up.
|
||||
* @param filename file to send
|
||||
* @param writeDelayMs how long to wait between each .write (0 for no delay)
|
||||
* @param serverDestFile file containing the StreamSinkServer's binary Destination
|
||||
*/
|
||||
public StreamSinkSend(String filename, int writeDelayMs, String serverDestFile) {
|
||||
_sendFile = filename;
|
||||
_writeDelay = writeDelayMs;
|
||||
_peerDestFile = serverDestFile;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkClient.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually connect and run the client - this call blocks until completion.
|
||||
*
|
||||
*/
|
||||
public void runClient() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination peer = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_peerDestFile);
|
||||
peer = new Destination();
|
||||
peer.readBytes(fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error finding the peer destination to contact in " + _peerDestFile, ioe);
|
||||
return;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Peer destination is not valid in " + _peerDestFile, dfe);
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
|
||||
System.out.println("Send " + _sendFile + " to " + peer.calculateHash().toBase64());
|
||||
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[32*1024];
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
fis = new FileInputStream(_sendFile);
|
||||
long size = 0;
|
||||
while (true) {
|
||||
int read = fis.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
out.write(buf, 0, read);
|
||||
size += read;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + read);
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
fis.close();
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient sendFile writeDelayMs serverDestFile</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendFile</b>: filename to send</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.out.println("Usage: StreamSinkClient sendFile writeDelayMs serverDestFile");
|
||||
} else {
|
||||
int writeDelayMs = -1;
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
StreamSinkSend client = new StreamSinkSend(args[0], writeDelayMs, args[2]);
|
||||
client.runClient();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -23,6 +24,8 @@ public class StreamSinkServer {
|
||||
private Log _log;
|
||||
private String _sinkDir;
|
||||
private String _destFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
|
||||
/**
|
||||
* Create but do not start the streaming server.
|
||||
@ -31,8 +34,13 @@ public class StreamSinkServer {
|
||||
* @param ourDestFile filename to write our binary destination to
|
||||
*/
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile) {
|
||||
this(sinkDir, ourDestFile, null, -1);
|
||||
}
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) {
|
||||
_sinkDir = sinkDir;
|
||||
_destFile = ourDestFile;
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
|
||||
}
|
||||
|
||||
@ -42,7 +50,11 @@ public class StreamSinkServer {
|
||||
*
|
||||
*/
|
||||
public void runServer() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
I2PSocketManager mgr = null;
|
||||
if (_i2cpHost != null)
|
||||
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination dest = mgr.getSession().getMyDestination();
|
||||
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
|
||||
FileOutputStream fos = null;
|
||||
@ -95,6 +107,7 @@ public class StreamSinkServer {
|
||||
sink.mkdirs();
|
||||
File cur = File.createTempFile("clientSink", ".dat", sink);
|
||||
_fos = new FileOutputStream(cur);
|
||||
System.out.println("Writing to " + cur.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating sink", ioe);
|
||||
_fos = null;
|
||||
@ -105,31 +118,53 @@ public class StreamSinkServer {
|
||||
try {
|
||||
InputStream in = _sock.getInputStream();
|
||||
byte buf[] = new byte[4096];
|
||||
long written = 0;
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
_fos.write(buf, 0, read);
|
||||
written += read;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read and wrote " + read);
|
||||
}
|
||||
_log.error("Got EOF from client socket [written=" + written + "]");
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing the sink", ioe);
|
||||
} finally {
|
||||
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
|
||||
if (_sock != null) try { _sock.close(); } catch (IOException ioe) {}
|
||||
_log.error("Client socket closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer sinkDir ourDestFile</code><br />
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br />
|
||||
* <ul>
|
||||
* <li><b>sinkDir</b>: Directory to store received files in</li>
|
||||
* <li><b>ourDestFile</b>: filename to write our binary destination to</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Usage: StreamSinkServer sinkDir ourDestFile");
|
||||
} else {
|
||||
StreamSinkServer server = new StreamSinkServer(args[0], args[1]);
|
||||
server.runServer();
|
||||
StreamSinkServer server = null;
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
server = new StreamSinkServer("dataDir", "server.key", "localhost", 10001);
|
||||
break;
|
||||
case 2:
|
||||
server = new StreamSinkServer(args[0], args[1]);
|
||||
break;
|
||||
case 4:
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
server = new StreamSinkServer(args[2], args[3], args[0], port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
|
||||
}
|
||||
if (server != null)
|
||||
server.runServer();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,218 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Sit around on a destination, receiving lots of data and sending lots of
|
||||
* data to whomever talks to us.
|
||||
*
|
||||
* Usage: TestSwarm myKeyFile [peerDestFile ]*
|
||||
*
|
||||
*/
|
||||
public class TestSwarm {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _destFile;
|
||||
private String _peerDestFiles[];
|
||||
private String _conOptions;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _dead;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: TestSwarm myDestFile [peerDestFile ]*");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, files, 0, files.length);
|
||||
TestSwarm swarm = new TestSwarm(ctx, args[0], files);
|
||||
swarm.startup();
|
||||
}
|
||||
|
||||
public TestSwarm(I2PAppContext ctx, String destFile, String peerDestFiles[]) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(TestSwarm.class);
|
||||
_dead = false;
|
||||
_destFile = destFile;
|
||||
_peerDestFiles = peerDestFiles;
|
||||
_conOptions = "";
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
File keys = new File(_destFile);
|
||||
if (!keys.exists()) {
|
||||
try {
|
||||
I2PClientFactory.createClient().createDestination(new FileOutputStream(keys));
|
||||
} catch (Exception e) {
|
||||
_log.error("Error creating a new destination on " + keys, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
_manager = I2PSocketManagerFactory.createManager(new FileInputStream(_destFile), null, -1, null);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error creatign the manager", e);
|
||||
return;
|
||||
}
|
||||
|
||||
I2PThread listener = new I2PThread(new Listener(), "Listener");
|
||||
listener.start();
|
||||
|
||||
connectWithPeers();
|
||||
}
|
||||
|
||||
|
||||
private void connectWithPeers() {
|
||||
if (_peerDestFiles != null) {
|
||||
for (int i = 0; i < _peerDestFiles.length; i++) {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(fin);
|
||||
|
||||
I2PThread flooder = new I2PThread(new Flooder(dest), "Flooder+" + dest.calculateHash().toBase64().substring(0,4));
|
||||
flooder.start();
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to read the peer from " + _peerDestFiles[i], e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Listener implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
I2PServerSocket ss = _manager.getServerSocket();
|
||||
I2PSocket s = null;
|
||||
while ( (s = ss.accept()) != null) {
|
||||
I2PThread flooder = new I2PThread(new Flooder(s), "Flooder-" + s.getPeerDestination().calculateHash().toBase64().substring(0,4));
|
||||
flooder.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error listening", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __conId = 0;
|
||||
private class Flooder implements Runnable {
|
||||
private Destination _remoteDestination;
|
||||
private I2PSocket _socket;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
private long _totalReceived;
|
||||
private long _lastReceived;
|
||||
private long _lastReceivedOn;
|
||||
private long _connectionId;
|
||||
|
||||
public Flooder(Destination dest) {
|
||||
_socket = null;
|
||||
_remoteDestination = dest;
|
||||
_connectionId = ++__conId;
|
||||
_closed = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public Flooder(I2PSocket socket) {
|
||||
_socket = socket;
|
||||
_remoteDestination = socket.getPeerDestination();
|
||||
_connectionId = ++__conId;
|
||||
_closed = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public long getConnectionId() { return _connectionId; }
|
||||
public Destination getDestination() { return _remoteDestination; }
|
||||
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[32*1024];
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
if (_socket == null) {
|
||||
try {
|
||||
_socket = _manager.connect(_remoteDestination);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error connecting to " + _remoteDestination.calculateHash().toBase64().substring(0,4));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
I2PThread floodListener = new I2PThread(new FloodListener(), "FloodListener" + _connectionId);
|
||||
floodListener.start();
|
||||
|
||||
try {
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
while (!_closed) {
|
||||
out.write(data);
|
||||
// out.flush();
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error sending", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class FloodListener implements Runnable {
|
||||
public void run() {
|
||||
long lastRead = System.currentTimeMillis();
|
||||
long now = lastRead;
|
||||
try {
|
||||
InputStream in = _socket.getInputStream();
|
||||
byte buf[] = new byte[32*1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
now = System.currentTimeMillis();
|
||||
_totalReceived += read;
|
||||
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
|
||||
_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
|
||||
lastRead = now;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error listening to the flood", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -206,11 +206,11 @@ public class NetMonitor {
|
||||
}
|
||||
|
||||
/** drop all the old summary data */
|
||||
public void coallesceData() {
|
||||
public void coalesceData() {
|
||||
synchronized (_peerSummaries) {
|
||||
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
|
||||
PeerSummary summary = (PeerSummary)iter.next();
|
||||
summary.coallesceData(_summaryDurationHours * 60*60*1000);
|
||||
summary.coalesceData(_summaryDurationHours * 60*60*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class NetMonitorRunner implements Runnable {
|
||||
long nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
while (_monitor.isRunning()) {
|
||||
now = Clock.getInstance().now();
|
||||
_monitor.coallesceData();
|
||||
_monitor.coalesceData();
|
||||
if (now >= nextHarvest) {
|
||||
runHarvest();
|
||||
nextHarvest = now + _monitor.getHarvestDelay() * 1000;
|
||||
|
@ -22,7 +22,7 @@ public class PeerSummary {
|
||||
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
|
||||
private Map _stats;
|
||||
/** lock on this when accessing stat data */
|
||||
private Object _coallesceLock = new Object();
|
||||
private Object _coalesceLock = new Object();
|
||||
|
||||
public PeerSummary(String peer) {
|
||||
_peer = peer;
|
||||
@ -38,7 +38,7 @@ public class PeerSummary {
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
@ -53,7 +53,7 @@ public class PeerSummary {
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
@ -68,7 +68,7 @@ public class PeerSummary {
|
||||
*
|
||||
*/
|
||||
public List getData(String statName) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new ArrayList(((TreeMap)_stats.get(statName)).values());
|
||||
}
|
||||
}
|
||||
@ -78,21 +78,21 @@ public class PeerSummary {
|
||||
*
|
||||
*/
|
||||
public Set getStatNames() {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new HashSet(_stats.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** drop old data points */
|
||||
public void coallesceData(long summaryDurationMs) {
|
||||
public void coalesceData(long summaryDurationMs) {
|
||||
long earliest = Clock.getInstance().now() - summaryDurationMs;
|
||||
synchronized (_coallesceLock) {
|
||||
locked_coallesce(earliest);
|
||||
synchronized (_coalesceLock) {
|
||||
locked_coalesce(earliest);
|
||||
}
|
||||
}
|
||||
|
||||
/** go through all the stats and remove ones from before the given date */
|
||||
private void locked_coallesce(long earliestSampleDate) {
|
||||
private void locked_coalesce(long earliestSampleDate) {
|
||||
if (true) return;
|
||||
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
@ -116,4 +116,4 @@ public class PeerSummary {
|
||||
_stats.put(statName, new TreeMap());
|
||||
return (TreeMap)_stats.get(statName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ class PeerSummaryReader {
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
summary.coalesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
monitor.addSummary(summary);
|
||||
}
|
||||
|
||||
@ -103,4 +103,4 @@ class PeerSummaryReader {
|
||||
return _fmt.parse(when).getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../../../router/java/build/router.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty-jdk1.2.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../systray/java/build/systray.jar" />
|
||||
<pathelement location="../../systray/java/lib/systray4j.jar" />
|
||||
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" /> <!-- we dont care if we're not on win32 -->
|
||||
@ -54,6 +55,7 @@
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/ant.jar" />
|
||||
<pathelement location="../../systray/java/build/obj" />
|
||||
<pathelement location="../../systray/java/lib/systray4j.jar" /> <!-- some javacs resolve recursively... -->
|
||||
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" /> <!-- we dont care if we're not on win32 -->
|
||||
<pathelement location="build/routerconsole.jar" />
|
||||
</classpath>
|
||||
|
@ -11,6 +11,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
private String _numClients;
|
||||
private String _numTunnels;
|
||||
private String _numHops;
|
||||
private String _numHopsOutbound;
|
||||
private boolean _shouldSave;
|
||||
|
||||
public void ConfigNetHandler() {
|
||||
@ -36,6 +37,9 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
public void setTunneldepth(String num) {
|
||||
_numHops = (num != null ? num.trim() : null);
|
||||
}
|
||||
public void setTunneldepthoutbound(String num) {
|
||||
_numHopsOutbound = (num != null ? num.trim() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user made changes to the network config and wants to save them, so
|
||||
@ -63,6 +67,12 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
saveRequired = true;
|
||||
}
|
||||
|
||||
if ( (_numHopsOutbound != null) && (_numHopsOutbound.length() > 0) ) {
|
||||
_context.router().setConfigSetting(ClientTunnelSettings.PROP_DEPTH_OUTBOUND, _numHopsOutbound);
|
||||
addFormNotice("Updating default outbound tunnel length to " + _numHopsOutbound);
|
||||
saveRequired = true;
|
||||
}
|
||||
|
||||
if (saveRequired) {
|
||||
boolean saved = _context.router().saveConfig();
|
||||
if (saved)
|
||||
|
@ -114,4 +114,31 @@ public class ConfigClientsHelper {
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String getTunnelDepthOutboundSelectBox() {
|
||||
int count = ClientTunnelSettings.DEFAULT_DEPTH_OUTBOUND;
|
||||
String val = _context.router().getConfigSetting(ClientTunnelSettings.PROP_DEPTH_OUTBOUND);
|
||||
if (val != null) {
|
||||
try {
|
||||
count = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore, use default from above
|
||||
}
|
||||
}
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("<select name=\"tunneldepthoutbound\">\n");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
buf.append("<option value=\"").append(i).append("\" ");
|
||||
if (count == i)
|
||||
buf.append("selected=\"true\" ");
|
||||
buf.append(">").append(i).append("</option>\n");
|
||||
}
|
||||
if (count >= 4) {
|
||||
buf.append("<option value=\"").append(count);
|
||||
buf.append("\" selected>").append(count);
|
||||
buf.append("</option>\n");
|
||||
}
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,10 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
if ( (_port != null) && (_port.length() > 0) ) {
|
||||
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_TCP_PORT);
|
||||
if ( (oldPort == null) || (!oldPort.equalsIgnoreCase(_port)) ) {
|
||||
if ( (oldPort == null) && (_port.equals("8887")) ) {
|
||||
// still on default.. noop
|
||||
} else if ( (oldPort == null) || (!oldPort.equalsIgnoreCase(_port)) ) {
|
||||
// its not the default OR it has changed
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_TCP_PORT, _port);
|
||||
addFormNotice("Updating TCP port from " + oldPort + " to " + _port);
|
||||
restartRequired = true;
|
||||
|
@ -120,15 +120,17 @@ public class ConfigNetHelper {
|
||||
private static String getBurstFactor(int numSeconds, String name) {
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
buf.append("<select name=\"").append(name).append("\">\n");
|
||||
for (int i = 1; i < 10; i++) {
|
||||
boolean found = false;
|
||||
for (int i = 10; i <= 60; i += 10) {
|
||||
buf.append("<option value=\"").append(i).append("\" ");
|
||||
if ( (i == numSeconds) || (i == 10) )
|
||||
if (i == numSeconds) {
|
||||
buf.append("selected ");
|
||||
found = true;
|
||||
} else if ( (i == 60) && (!found) ) {
|
||||
buf.append("selected ");
|
||||
}
|
||||
buf.append(">");
|
||||
if (i == 1)
|
||||
buf.append("1 second (no burst)</option>\n");
|
||||
else
|
||||
buf.append(i).append(" seconds</option>\n");
|
||||
buf.append(i).append(" seconds</option>\n");
|
||||
}
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
|
@ -1,10 +1,17 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.ClientTunnelSettings;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.apps.systray.SysTray;
|
||||
import net.i2p.apps.systray.UrlLauncher;
|
||||
import org.tanukisoftware.wrapper.WrapperManager;
|
||||
|
||||
/**
|
||||
@ -86,6 +93,12 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
} catch (Throwable t) {
|
||||
addFormError("Warning: unable to contact the systray manager - " + t.getMessage());
|
||||
}
|
||||
} else if ("View console on startup".equals(_action)) {
|
||||
browseOnStartup(true);
|
||||
addFormNotice("Console is to be shown on startup");
|
||||
} else if ("Do not view console on startup".equals(_action)) {
|
||||
browseOnStartup(false);
|
||||
addFormNotice("Console is not to be shown on startup");
|
||||
} else {
|
||||
addFormNotice("Blah blah blah. whatever. I'm not going to " + _action);
|
||||
}
|
||||
@ -107,4 +120,81 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
addFormError("Warning: unable to remove the service - " + ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private final static String NL = System.getProperty("line.separator");
|
||||
private void browseOnStartup(boolean shouldLaunchBrowser) {
|
||||
File f = new File("clients.config");
|
||||
Properties p = new Properties();
|
||||
try {
|
||||
DataHelper.loadProps(p, f);
|
||||
|
||||
int i = 0;
|
||||
int launchIndex = -1;
|
||||
while (true) {
|
||||
String className = p.getProperty("clientApp." + i + ".main");
|
||||
if (className == null) break;
|
||||
if (UrlLauncher.class.getName().equals(className)) {
|
||||
launchIndex = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if ((launchIndex >= 0) && shouldLaunchBrowser)
|
||||
return;
|
||||
if ((launchIndex < 0) && !shouldLaunchBrowser)
|
||||
return;
|
||||
|
||||
if (shouldLaunchBrowser) {
|
||||
p.setProperty("clientApp." + i + ".main", UrlLauncher.class.getName());
|
||||
p.setProperty("clientApp." + i + ".name", "BrowserLauncher");
|
||||
p.setProperty("clientApp." + i + ".args", "http://localhost:7657/index.jsp");
|
||||
p.setProperty("clientApp." + i + ".delay", "5");
|
||||
} else {
|
||||
p.remove("clientApp." + launchIndex + ".main");
|
||||
p.remove("clientApp." + launchIndex + ".name");
|
||||
p.remove("clientApp." + launchIndex + ".args");
|
||||
p.remove("clientApp." + launchIndex + ".onBoot");
|
||||
p.remove("clientApp." + launchIndex + ".delay");
|
||||
|
||||
i = launchIndex + 1;
|
||||
while (true) {
|
||||
String main = p.getProperty("clientApp." + i + ".main");
|
||||
String name = p.getProperty("clientApp." + i + ".name");
|
||||
String args = p.getProperty("clientApp." + i + ".args");
|
||||
String boot = p.getProperty("clientApp." + i + ".onBoot");
|
||||
String delay= p.getProperty("clientApp." + i + ".delay");
|
||||
|
||||
if (main == null) break;
|
||||
|
||||
p.setProperty("clientApp." + (i-1) + ".main", main);
|
||||
p.setProperty("clientApp." + (i-1) + ".name", name);
|
||||
p.setProperty("clientApp." + (i-1) + ".args", args);
|
||||
if (boot != null)
|
||||
p.setProperty("clientApp." + (i-1) + ".onBoot", boot);
|
||||
if (delay != null)
|
||||
p.setProperty("clientApp." + (i-1) + ".delay", delay);
|
||||
|
||||
p.remove("clientApp." + i + ".main");
|
||||
p.remove("clientApp." + i + ".name");
|
||||
p.remove("clientApp." + i + ".args");
|
||||
p.remove("clientApp." + i + ".onBoot");
|
||||
p.remove("clientApp." + i + ".delay");
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TreeMap sorted = new TreeMap(p);
|
||||
FileWriter out = new FileWriter(f);
|
||||
for (Iterator iter = sorted.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String val = (String)sorted.get(name);
|
||||
out.write(name + "=" + val + NL);
|
||||
}
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
addFormError("Error updating the client config");
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import net.i2p.util.FileUtil;
|
||||
public class ContentHelper {
|
||||
private String _page;
|
||||
private int _maxLines;
|
||||
private boolean _startAtBeginning;
|
||||
private RouterContext _context;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
@ -28,6 +29,10 @@ public class ContentHelper {
|
||||
public ContentHelper() {}
|
||||
|
||||
public void setPage(String page) { _page = page; }
|
||||
public void setStartAtBeginning(String moo) {
|
||||
_startAtBeginning = Boolean.valueOf(""+moo).booleanValue();
|
||||
}
|
||||
|
||||
public void setMaxLines(String lines) {
|
||||
if (lines != null) {
|
||||
try {
|
||||
@ -40,14 +45,14 @@ public class ContentHelper {
|
||||
}
|
||||
}
|
||||
public String getContent() {
|
||||
String str = FileUtil.readTextFile(_page, _maxLines);
|
||||
String str = FileUtil.readTextFile(_page, _maxLines, _startAtBeginning);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
return str;
|
||||
}
|
||||
public String getTextContent() {
|
||||
String str = FileUtil.readTextFile(_page, _maxLines);
|
||||
String str = FileUtil.readTextFile(_page, _maxLines, _startAtBeginning);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
|
@ -42,7 +42,7 @@ public class LogsHelper {
|
||||
}
|
||||
|
||||
public String getServiceLogs() {
|
||||
String str = FileUtil.readTextFile("wrapper.log", 500);
|
||||
String str = FileUtil.readTextFile("wrapper.log", 500, false);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
|
@ -0,0 +1,129 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler to deal with reseed requests. This reseed from the URL
|
||||
* http://dev.i2p.net/i2pdb/ unless the java env property "i2p.reseedURL" is
|
||||
* set. It always writes to ./netDb/, so don't mess with that.
|
||||
*
|
||||
*/
|
||||
public class ReseedHandler {
|
||||
private static ReseedRunner _reseedRunner = new ReseedRunner();
|
||||
|
||||
public void setReseedNonce(String nonce) {
|
||||
if (nonce == null) return;
|
||||
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
|
||||
nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.noncePrev"))) {
|
||||
synchronized (_reseedRunner) {
|
||||
if (_reseedRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
|
||||
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
|
||||
reseed.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReseedRunner implements Runnable {
|
||||
private boolean _isRunning;
|
||||
public ReseedRunner() { _isRunning = false; }
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
reseed();
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DEFAULT_SEED_URL = "http://dev.i2p.net/i2pdb/";
|
||||
/**
|
||||
* Reseed has been requested, so lets go ahead and do it. Fetch all of
|
||||
* the routerInfo-*.dat files from the specified URL (or the default) and
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private static void reseed() {
|
||||
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
|
||||
seedURL = DEFAULT_SEED_URL;
|
||||
try {
|
||||
URL dir = new URL(seedURL);
|
||||
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;
|
||||
}
|
||||
|
||||
int fetched = 0;
|
||||
int errors = 0;
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fetchSeed(String seedURL, String peer) throws Exception {
|
||||
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
|
||||
|
||||
byte data[] = readURL(url);
|
||||
writeSeed(peer, data);
|
||||
}
|
||||
|
||||
private static 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 static void writeSeed(String name, byte data[]) throws Exception {
|
||||
String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
|
||||
File netDbDir = new File(dirName);
|
||||
if (!netDbDir.exists()) {
|
||||
boolean ok = netDbDir.mkdirs();
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/**
|
||||
* uuuugly. dump the peer profile data if given a peer.
|
||||
*
|
||||
*/
|
||||
public class StatHelper {
|
||||
private String _peer;
|
||||
private Writer _writer;
|
||||
|
||||
public void setPeer(String peer) { _peer = peer; }
|
||||
public void setWriter(Writer writer) { _writer = writer; }
|
||||
|
||||
public String getProfile() {
|
||||
RouterContext ctx = (RouterContext)net.i2p.router.RouterContext.listContexts().get(0);
|
||||
Set peers = ctx.profileOrganizer().selectAllPeers();
|
||||
for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
if (peer.toBase64().startsWith(_peer)) {
|
||||
try {
|
||||
WriterOutputStream wos = new WriterOutputStream(_writer);
|
||||
ctx.profileOrganizer().exportProfile(peer, wos);
|
||||
wos.flush();
|
||||
_writer.flush();
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Treat a writer as an output stream. Quick 'n dirty, none
|
||||
* of that "intarnasheeonaleyzayshun" stuff. So we can treat
|
||||
* the jsp's PrintWriter as an OutputStream
|
||||
*/
|
||||
public class WriterOutputStream extends OutputStream {
|
||||
private Writer _writer;
|
||||
|
||||
public WriterOutputStream(Writer writer) { _writer = writer; }
|
||||
public void write(int b) throws IOException { _writer.write(b); }
|
||||
}
|
@ -28,44 +28,58 @@
|
||||
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigNetHandler.nonce")%>" />
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
|
||||
<b>External hostname/IP address:</b>
|
||||
<input name="hostname" type="text" size="32" value="<jsp:getProperty name="nethelper" property="hostname" />" />
|
||||
<input type="submit" name="guesshost" value="Guess" /><br />
|
||||
<b>Externally reachable TCP port:</b>
|
||||
TCP port:
|
||||
<input name="port" type="text" size="4" value="<jsp:getProperty name="nethelper" property="port" />" /> <br />
|
||||
<i>The hostname/IP address and TCP port must be reachable from the outside world. If
|
||||
you are behind a firewall or NAT, this means you must poke a hole for this port. If
|
||||
you are using DHCP and do not have a static IP address, you should either use a service like
|
||||
<a href="http://dyndns.org/">dyndns</a> or leave the hostname blank. If you leave it blank,
|
||||
your router will autodetect the 'correct' IP address by asking a peer (and unconditionally
|
||||
believing them if the address is routable and you don't have any established connections yet).
|
||||
The "guess" functionality makes an HTTP request
|
||||
to <a href="http://www.whatismyip.com/">www.whatismyip.com</a>.</i>
|
||||
<hr />
|
||||
<b>Enable internal time synchronization?</b> <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
|
||||
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
|
||||
be within a few seconds of "correct".</i>
|
||||
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
|
||||
connections on it.</b> Nothing will work if you don't. Sorry. We know how to make it so
|
||||
this restriction won't be necessary, but its later on in the
|
||||
<a href="http://www.i2p.net/roadmap">roadmap</a> and we only have so many coder-hours (but if you want
|
||||
to help, please <a href="http://www.i2p.net/getinvolved">get involved!</a>)
|
||||
<hr />
|
||||
|
||||
<b>Bandwidth limiter</b><br />
|
||||
<b>Inbound rate</b>:
|
||||
Inbound rate:
|
||||
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBytes per second<br />
|
||||
<b>Inbound burst duration:</b>
|
||||
Inbound burst duration:
|
||||
<jsp:getProperty name="nethelper" property="inboundBurstFactorBox" /><br />
|
||||
<b>Outbound rate:</b>
|
||||
Outbound rate:
|
||||
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBytes per second<br />
|
||||
<b>Outbound burst duration:</b>
|
||||
Outbound burst duration:
|
||||
<jsp:getProperty name="nethelper" property="outboundBurstFactorBox" /><br />
|
||||
<i>A negative rate means there is no limit</i><br />
|
||||
<hr />
|
||||
<b>Reseed</b> (from <input name="reseedfrom" type="text" size="40" value="http://dev.i2p.net/i2pdb/" />):
|
||||
<input type="submit" name="reseed" value="now" /><br />
|
||||
<i>May take some time to download the peer references</i>
|
||||
Enable internal time synchronization? <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
|
||||
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
|
||||
be within a few seconds of "correct". You will need to be able to send outbound UDP
|
||||
packets on port 123 to one of the pool.ntp.org machines (or some other SNTP server).</i>
|
||||
<hr />
|
||||
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
|
||||
<i>Changing the hostname or TCP port will force a 'soft restart' - dropping your connections
|
||||
and clients as if the router was stopped and restarted. <b>Please be patient</b> - it may take
|
||||
<i>Changing the TCP port will force a 'soft restart' - dropping your connections and clients as
|
||||
if the router was stopped and restarted. <b>Please be patient</b> - it may take
|
||||
a few seconds to complete.</i>
|
||||
</form>
|
||||
<hr />
|
||||
<b>Advanced network config:</b>
|
||||
<p>
|
||||
There are two other network settings, but no one reads this text so there's no reason
|
||||
to tell you about them. In case you actually do read this, here's the deal: by default,
|
||||
I2P will attempt to guess your IP address by having whomever it talks to tell it what
|
||||
address they think you are. If and only if you have no working TCP connections and you
|
||||
have not overridden the IP address, your router will believe them. If that doesn't sound
|
||||
ok to you, thats fine - go to the <a href="configadvanced.jsp">advanced config</a> page
|
||||
and add "i2np.tcp.hostname=yourHostname", then go to the
|
||||
<a href="configservice.jsp">service</a> page and do a graceful restart. We used to make
|
||||
people enter a hostname/IP address on this page, but too many people got it wrong ;)</p>
|
||||
|
||||
<p>The other advanced network option has to do with reseeding - you should never need to
|
||||
reseed your router as long as you can find at least one other peer on the network. However,
|
||||
when you do need to reseed, a link will show up on the left hand side which will
|
||||
fetch all of the routerInfo-* files from http://dev.i2p.net/i2pdb/. That URL is just an
|
||||
apache folder pointing at the netDb/ directory of a router - anyone can run one, and you can
|
||||
configure your router to seed off an alternate URL by adding the java environmental property
|
||||
"i2p.reseedURL=someURL" (e.g. java -Di2p.reseedURL=http://dev.i2p.net/i2pdb/ ...). You can
|
||||
also do it manually by getting routerInfo-*.dat files from someone (a friend, someone on IRC,
|
||||
whatever) and saving them to your netDb/ directory.</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
@ -29,10 +29,12 @@
|
||||
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigAdvancedHandler.nonce")%>" />
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
<textarea rows="20" cols="100" name="config"><jsp:getProperty name="advancedhelper" property="settings" /></textarea><br />
|
||||
<input type="submit" name="shouldsave" value="Apply" /> <input type="reset" value="Cancel" /> <br />
|
||||
<input type="submit" name="shouldsave" value="Apply" /> <input type="reset" value="Cancel" /><!-- <br />
|
||||
<b>Force restart:</b> <input type="checkbox" name="restart" value="force" /> <i>(specify this
|
||||
if the changes made above require the router to reset itself - e.g. you are updating TCP ports
|
||||
or hostnames, etc)</i>
|
||||
or hostnames, etc)</i>-->
|
||||
If you are changing any of the I2NP settings, you should go to the
|
||||
<a href="configservice.jsp">service config</a> page and do a graceful restart after saving.
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -34,6 +34,8 @@
|
||||
<jsp:getProperty name="clientshelper" property="tunnelCountSelectBox" /><br />
|
||||
<b>Default number of hops per tunnel:</b>
|
||||
<jsp:getProperty name="clientshelper" property="tunnelDepthSelectBox" /><br />
|
||||
<b>Hops per outbound tunnel:</b>
|
||||
<jsp:getProperty name="clientshelper" property="tunnelDepthOutboundSelectBox" /><br />
|
||||
<hr />
|
||||
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
|
||||
</form>
|
||||
|
@ -67,6 +67,14 @@
|
||||
please select the following option and review the thread dumped to
|
||||
<a href="logs.jsp#servicelogs">wrapper.log</a>.</p>
|
||||
<input type="submit" name="action" value="Dump threads" />
|
||||
|
||||
<h4>Launch browser on router startup?</h4>
|
||||
<p>I2P's main configuration interface is this web console, so for your convenience
|
||||
I2P can launch a web browser pointing at
|
||||
<a href="http://localhost:7657/index.jsp">http://localhost:7657/index.jsp</a> whenever
|
||||
the router starts up.</p>
|
||||
<input type="submit" name="action" value="View console on startup" />
|
||||
<input type="submit" name="action" value="Do not view console on startup" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
5
apps/routerconsole/jsp/dumpprofile.jsp
Normal file
5
apps/routerconsole/jsp/dumpprofile.jsp
Normal file
@ -0,0 +1,5 @@
|
||||
<%@page contentType="text/plain"
|
||||
%><jsp:useBean id="helper" class="net.i2p.router.web.StatHelper"
|
||||
/><jsp:setProperty name="helper" property="peer" value="<%=request.getParameter("peer")%>"
|
||||
/><jsp:setProperty name="helper" property="writer" value="<%=out%>"
|
||||
/><jsp:getProperty name="helper" property="profile" />
|
BIN
apps/routerconsole/jsp/favicon.ico
Normal file
BIN
apps/routerconsole/jsp/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -33,11 +33,13 @@ by their binary code license. This product includes software developed by the A
|
||||
(http://www.apache.org/). </p>
|
||||
|
||||
<p>Another application you can see on this webpage is <a href="http://www.i2p.net/i2ptunnel">I2PTunnel</a>
|
||||
(your <a href="/i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
|
||||
(your <a href="i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
|
||||
lets you tunnel normal TCP/IP traffic over I2P (such as the eepproxy and the irc proxy).</p>
|
||||
|
||||
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
|
||||
which other client applications (such as aum's <a href="http://stasher.i2p/">stasher</a>) can use. For
|
||||
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
|
||||
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
|
||||
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. For
|
||||
details on other applications available, as well as their licenses, please see the
|
||||
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
|
||||
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is
|
||||
@ -47,7 +49,14 @@ in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
|
||||
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
|
||||
<jsp:setProperty name="contenthelper" property="page" value="history.txt" />
|
||||
<jsp:setProperty name="contenthelper" property="maxLines" value="500" />
|
||||
<jsp:setProperty name="contenthelper" property="startAtBeginning" value="true" />
|
||||
<jsp:getProperty name="contenthelper" property="textContent" />
|
||||
|
||||
<p>
|
||||
A more complete list of updates can be found
|
||||
<a href="http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">online</a>
|
||||
(<a href="http://dev.i2p/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">anonymously</a>)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
@ -2,6 +2,9 @@
|
||||
<jsp:useBean class="net.i2p.router.web.SummaryHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ReseedHandler" id="reseed" scope="request" />
|
||||
<jsp:setProperty name="reseed" property="*" />
|
||||
|
||||
<div class="routersummary">
|
||||
<u><b>General</b></u><br />
|
||||
<b>Ident:</b> <jsp:getProperty name="helper" property="ident" /><br />
|
||||
@ -16,8 +19,27 @@
|
||||
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
|
||||
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
|
||||
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
|
||||
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
|
||||
<hr />
|
||||
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /><%
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
|
||||
}
|
||||
if (helper.getActiveProfiles() <= 10) { // 10 is the min fallback
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
|
||||
out.print(" <i>reseeding</i>");
|
||||
} else {
|
||||
long nonce = new java.util.Random().nextLong();
|
||||
String prev = System.getProperty("net.i2p.router.web.ReseedHandler.nonce");
|
||||
if (prev != null) System.setProperty("net.i2p.router.web.ReseedHandler.noncePrev", prev);
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.nonce", nonce+"");
|
||||
String uri = request.getRequestURI();
|
||||
if (uri.indexOf('?') > 0)
|
||||
uri = uri + "&reseedNonce=" + nonce;
|
||||
else
|
||||
uri = uri + "?reseedNonce=" + nonce;
|
||||
out.print(" <a href=\"" + uri + "\">reseed</a>");
|
||||
}
|
||||
}
|
||||
%><hr />
|
||||
|
||||
<u><b>Bandwidth in/out</b></u><br />
|
||||
<b>1m:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
|
||||
|
@ -21,9 +21,9 @@ SRCDIR = src
|
||||
# Programs
|
||||
#
|
||||
|
||||
AR = C:\Dev-Cpp\bin\ar
|
||||
CC = C:\Dev-Cpp\bin\gcc
|
||||
RM = C:\Dev-Cpp\bin\rm
|
||||
AR = C:\MinGW\bin\ar
|
||||
CC = C:\MinGW\bin\gcc
|
||||
RM = C:\MinGW\bin\rm
|
||||
|
||||
#
|
||||
# Flags
|
||||
|
@ -1 +1 @@
|
||||
See the `docs' directory for documentation and license.
|
||||
See the `docs' directory for the documentation and license.
|
||||
|
@ -1,7 +0,0 @@
|
||||
If you would like to make a donation to the author of this library, you can use
|
||||
the following methods:
|
||||
|
||||
* E-Gold account number 1043280
|
||||
* Paypal email mpc@innographx.com
|
||||
|
||||
If you want to use some other method, just ask.
|
32
apps/sam/c/doc/install.txt
Normal file
32
apps/sam/c/doc/install.txt
Normal file
@ -0,0 +1,32 @@
|
||||
=====================
|
||||
How to Install LibSAM
|
||||
=====================
|
||||
|
||||
1) Be sure you have GNU Make installed.
|
||||
|
||||
2) Find the Makefile for your operating system in the LibSAM root. For example,
|
||||
if you are on FreeBSD, you'd use Makefile.freebsd.
|
||||
|
||||
3) Run gmake with the -f option to build LibSAM. For example, on FreeBSD, you'd
|
||||
run "gmake -f Makefile.freebsd". On Linux, GNU Make is just called 'make', so
|
||||
you'd run "make -f Makefile.linux".
|
||||
|
||||
4) If that worked, you can now try to compile some of the example programs.
|
||||
They are compiled in the same way as LibSAM, but the Makefile names are
|
||||
different.
|
||||
|
||||
I2P-Ping should compile on any Unix system using the default Makefile. It won't
|
||||
work on Win32, however, because Win32 doesn't have the getopt() function.
|
||||
|
||||
The Warhammer example should run on any OS. Use the Makefile.posix for Unix
|
||||
sytems or the Makefile.mingw for Win32.
|
||||
|
||||
*** If you have trouble ***
|
||||
|
||||
If you have trouble compiling LibSAM, try to edit the Makefiles to fix the
|
||||
problem. The "Makefile.common" is shared by all systems, and you shouldn't have
|
||||
to touch it. Just copy the Makefile of the OS most similar to your own and
|
||||
start hacking on it. Send me a patch if you get it working.
|
||||
|
||||
I realise this build system is horrible, and in the future I will probably
|
||||
replace it entirely.
|
@ -1,7 +1,10 @@
|
||||
I need to do these things:
|
||||
|
||||
* SAM raw support
|
||||
* SAM raw support (partially complete)
|
||||
* Write an instruction manual
|
||||
* Make dest a dynamic string
|
||||
* Change SAM parser to use a hashmap
|
||||
* Improve build system
|
||||
|
||||
Anyone can help with these things:
|
||||
|
||||
|
@ -1,7 +1,12 @@
|
||||
/* vi:set ts=4: */
|
||||
|
||||
v1.30
|
||||
* Added session to sam_namingback()
|
||||
* Removed stdint.h dependency
|
||||
* Improved WIRETAP to do more logging
|
||||
* Added "pinger.sh" shell script example for using i2p-ping
|
||||
* Added SAM_BAD_STYLE error
|
||||
* Added exit values for i2p-ping from xolo
|
||||
|
||||
v1.25 2004-07-31
|
||||
* Created I2P-Ping, a new example program (it's a clone of I2Ping). Works
|
||||
|
@ -60,7 +60,8 @@ static void databack(sam_sess_t *session, sam_sid_t stream_id, void *data,
|
||||
size_t size);
|
||||
static void diedback(sam_sess_t *session);
|
||||
static void logback(char *s);
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result);
|
||||
static void statusback(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t result);
|
||||
|
||||
@ -78,7 +79,7 @@ int main(int argc, char *argv[])
|
||||
int count = INT_MAX; /* number of times to ping */
|
||||
int pongcount = -1;
|
||||
char *samhost = "localhost";
|
||||
uint16_t samport = 7656;
|
||||
unsigned short samport = 7656;
|
||||
|
||||
while ((ch = getopt(argc, argv, "ac:h:mp:qv")) != -1) {
|
||||
switch (ch) {
|
||||
@ -103,7 +104,7 @@ int main(int argc, char *argv[])
|
||||
quiet = true;
|
||||
break;
|
||||
case 'v': /* version */
|
||||
puts("$Id: i2p-ping.c,v 1.4 2004/09/22 20:05:40 jrandom Exp $");
|
||||
puts("$Id: i2p-ping.c,v 1.6 2004/12/02 17:54:23 mpc Exp $");
|
||||
puts("Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>");
|
||||
break;
|
||||
case '?':
|
||||
@ -140,7 +141,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
pongcount = 0;
|
||||
for (int j = 0; j < argc; j++) {
|
||||
if (strlen(argv[j]) == 516) {
|
||||
if (strlen(argv[j]) == SAM_PUBKEY_LEN - 1) {
|
||||
memcpy(dest, argv[j], SAM_PUBKEY_LEN);
|
||||
gotdest = true;
|
||||
} else
|
||||
@ -242,7 +243,8 @@ static void logback(char *s)
|
||||
* This is really hackish, but we know that we are only doing one lookup, so
|
||||
* what the hell
|
||||
*/
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result)
|
||||
{
|
||||
if (result != SAM_OK) {
|
||||
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));
|
||||
|
60
apps/sam/c/examples/i2p-ping/pinger.sh
Normal file
60
apps/sam/c/examples/i2p-ping/pinger.sh
Normal file
@ -0,0 +1,60 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author nor the names of any contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# This script I use to ping all the hosts in the I2P hosts.txt file. You should
|
||||
# edit your hosts.txt and remove all the comments and blanks lines before
|
||||
# running this.
|
||||
#
|
||||
# You can set this up to run daily via cron. It takes a very long time to run
|
||||
# so I recommend starting it in the middle of the night.
|
||||
|
||||
OUTPUT=/tmp/index.html
|
||||
FINALOUT=$HOME/www/index.html
|
||||
|
||||
echo "<html>" > $OUTPUT
|
||||
echo "<head>" >> $OUTPUT
|
||||
echo "<title>I2P Weather Report</title>" >> $OUTPUT
|
||||
echo "</head>" >> $OUTPUT
|
||||
echo "<body>" >> $OUTPUT
|
||||
echo "<h1>I2P Weather Report</h1>" >> $OUTPUT
|
||||
echo "Date: " >> $OUTPUT
|
||||
date -u >> $OUTPUT
|
||||
echo "(refreshed daily)" >> $OUTPUT
|
||||
echo "<p>" >> $OUTPUT
|
||||
echo "If your site isn't listed, that means the ping failed (I2P error)" >> $OUTPUT
|
||||
echo "<p>" >> $OUTPUT
|
||||
echo "<pre>" >> $OUTPUT
|
||||
cut -d"=" -f1 $HOME/i2p/hosts.txt | tr "\n" " " | xargs $HOME/bin/i2p-ping -q -c 1 | grep -v "I2P error" >> $OUTPUT
|
||||
echo "</pre>" >> $OUTPUT
|
||||
echo "<p>" >> $OUTPUT
|
||||
echo "<i>Disclaimer: This only indicates accessibility from my router, not the network in general.</i>" >> $OUTPUT
|
||||
echo "</body>" >> $OUTPUT
|
||||
echo "</html>" >> $OUTPUT
|
||||
cp $OUTPUT $FINALOUT
|
@ -6,8 +6,8 @@
|
||||
# Programs
|
||||
#
|
||||
|
||||
CC = C:\Dev-Cpp\bin\gcc
|
||||
RM = C:\Dev-Cpp\bin\rm
|
||||
CC = C:\MinGW\bin\gcc
|
||||
RM = C:\MinGW\bin\rm
|
||||
|
||||
#
|
||||
# Flags
|
||||
|
@ -47,7 +47,8 @@ static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
size_t size);
|
||||
static void diedback(sam_sess_t *session);
|
||||
static void logback(char *s);
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result);
|
||||
|
||||
/*
|
||||
* Just some ugly global variables. Don't do this in your program.
|
||||
@ -88,12 +89,12 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether they've supplied a name or a base 64 destination
|
||||
* Check whether they've supplied a hostname or a base 64 destination
|
||||
*
|
||||
* Note that this is a hack. Jrandom says that once certificates are added,
|
||||
* the length could be different depending on the certificate's size.
|
||||
*/
|
||||
if (strlen(argv[1]) == 516) {
|
||||
if (strlen(argv[1]) == SAM_PUBKEY_LEN - 1) {
|
||||
memcpy(dest, argv[1], SAM_PUBKEY_LEN);
|
||||
gotdest = true;
|
||||
} else {
|
||||
@ -155,7 +156,6 @@ static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
static void diedback(sam_sess_t *session)
|
||||
{
|
||||
fprintf(stderr, "Lost SAM connection!\n");
|
||||
/* high quality code would do a sam_session_free() here */
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -172,11 +172,11 @@ static void logback(char *s)
|
||||
* This is really hackish, but we know that we are only doing one lookup, so
|
||||
* what the hell
|
||||
*/
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result)
|
||||
{
|
||||
if (result != SAM_OK) {
|
||||
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));
|
||||
/* high quality code would do a sam_session_free() here */
|
||||
exit(1);
|
||||
}
|
||||
memcpy(dest, pubkey, SAM_PUBKEY_LEN);
|
||||
|
@ -28,36 +28,19 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
#ifndef LIBSAM_PLATFORM_H
|
||||
#define LIBSAM_PLATFORM_H
|
||||
|
||||
/*
|
||||
* Operating system
|
||||
*/
|
||||
#define FREEBSD 0 // FreeBSD
|
||||
#define MINGW 1 // Windows native (Mingw)
|
||||
#define CYGWIN 1 // Cygwin
|
||||
#define LINUX 2 // Linux
|
||||
#define CYGWIN 3 // Cygwin
|
||||
|
||||
#if OS == MINGW
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_INET_ATON /* implies NO_INET_PTON */
|
||||
#define NO_INET_NTOP
|
||||
#define NO_SSIZE_T
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#define WINSOCK
|
||||
#endif
|
||||
|
||||
#if OS == LINUX
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#endif
|
||||
#define MINGW 3 // Windows native (Mingw)
|
||||
#define MSVC 4 // Windows native (Visual C++ 2003)
|
||||
|
||||
#if OS == CYGWIN
|
||||
#define FAST32_IS_LONG
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_INET_NTOP
|
||||
@ -68,13 +51,29 @@
|
||||
#define NO_Z_FORMAT
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Standard C99 includes - if your compiler doesn't have these, it's time to
|
||||
* upgrade
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#if OS == LINUX
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#endif
|
||||
|
||||
#if OS == MINGW
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_INET_ATON // implies NO_INET_PTON
|
||||
#define NO_INET_NTOP
|
||||
#define NO_SSIZE_T
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#define WINSOCK
|
||||
#endif
|
||||
|
||||
#if OS == MSVC // FIXME: doesn't work
|
||||
#define NO_STDBOOL_H
|
||||
#define NO_SSIZE_T
|
||||
#define NO_STRL
|
||||
#define WINSOCK
|
||||
#endif
|
||||
|
||||
/*
|
||||
* System includes
|
||||
@ -116,6 +115,13 @@
|
||||
typedef signed long ssize_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* I'm too lazy to type "unsigned"
|
||||
*/
|
||||
typedef unsigned char byte;
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned short ushort;
|
||||
|
||||
/*
|
||||
* Prints out the file name, line number, and function name before log message
|
||||
*/
|
||||
@ -136,4 +142,4 @@
|
||||
#include <ctype.h>
|
||||
#endif
|
||||
|
||||
#endif /* PLATFORM_H */
|
||||
#endif /* LIBSAM_PLATFORM_H */
|
||||
|
@ -28,18 +28,26 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SAM_H
|
||||
#define SAM_H
|
||||
#ifndef LIBSAM_SAM_H
|
||||
#define LIBSAM_SAM_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#ifdef NO_STDBOOL_H
|
||||
typedef int bool;
|
||||
#define true 0
|
||||
#define false 1
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
|
||||
/*
|
||||
* Lengths
|
||||
*/
|
||||
|
||||
/* The maximum length a SAM command can be */
|
||||
#define SAM_CMD_LEN 128
|
||||
/* The maximum size of a single datagram packet */
|
||||
@ -48,29 +56,21 @@ extern "C" {
|
||||
#define SAM_LOGMSG_LEN 256
|
||||
/* The longest `name' arg for the naming lookup callback */
|
||||
#define SAM_NAME_LEN 256
|
||||
/* The max size of a single stream packet */
|
||||
/* The maximum size of a single stream packet */
|
||||
#define SAM_STREAM_PAYLOAD_MAX (32 * 1024)
|
||||
/* The length of a base 64 public key - it's actually 516, but +1 for '\0' */
|
||||
#define SAM_PUBKEY_LEN 517
|
||||
/* A public key SAM command's length */
|
||||
/* The maximum length of a SAM command with a public key */
|
||||
#define SAM_PKCMD_LEN (SAM_PUBKEY_LEN + SAM_CMD_LEN)
|
||||
/* The maximum size of a single raw packet */
|
||||
#define SAM_RAW_PAYLOAD_MAX (32 * 1024)
|
||||
/* The maximum length a SAM non-data reply can be */
|
||||
#define SAM_REPLY_LEN 1024
|
||||
|
||||
/*
|
||||
* Shorten some standard variable types
|
||||
*/
|
||||
typedef signed char schar_t;
|
||||
typedef unsigned char uchar_t;
|
||||
typedef unsigned int uint_t;
|
||||
typedef unsigned long ulong_t;
|
||||
typedef unsigned short ushort_t;
|
||||
|
||||
#ifdef WINSOCK
|
||||
typedef SOCKET socket_t;
|
||||
#else
|
||||
typedef int socket_t;
|
||||
#endif
|
||||
/*
|
||||
* Some LibSAM variable types
|
||||
*/
|
||||
|
||||
typedef enum {SAM_STREAM, SAM_DGRAM, SAM_RAW} sam_conn_t; /* SAM connection */
|
||||
|
||||
@ -81,12 +81,13 @@ typedef struct {
|
||||
size_t size;
|
||||
} sam_sendq_t; /* sending queue to encourage large stream packet sizes */
|
||||
|
||||
typedef int_fast32_t sam_sid_t; /* stream id number */
|
||||
typedef long sam_sid_t; /* stream id number */
|
||||
|
||||
typedef struct {
|
||||
socket_t sock; /* the socket used for communications with SAM */
|
||||
int sock; /* the socket used for communications with SAM */
|
||||
bool connected; /* whether the socket is connected */
|
||||
sam_sid_t prev_id; /* the last stream id number we used */
|
||||
void *child; /* whatever you want it to be */
|
||||
} sam_sess_t; /* a SAM session */
|
||||
|
||||
typedef enum { /* see sam_strerror() for detailed descriptions of these */
|
||||
@ -101,59 +102,65 @@ typedef enum { /* see sam_strerror() for detailed descriptions of these */
|
||||
SAM_TOO_BIG
|
||||
} samerr_t;
|
||||
|
||||
|
||||
/*
|
||||
* Public functions
|
||||
*/
|
||||
|
||||
/* SAM controls - sessions */
|
||||
extern sam_sess_t *sam_session_init(sam_sess_t *session);
|
||||
extern void sam_session_free(sam_sess_t **session);
|
||||
sam_sess_t *sam_session_init(sam_sess_t *session);
|
||||
void sam_session_free(sam_sess_t **session);
|
||||
/* SAM controls - connection */
|
||||
extern bool sam_close(sam_sess_t *session);
|
||||
extern samerr_t sam_connect(sam_sess_t *session, const char *samhost,
|
||||
uint16_t samport, const char *destname, sam_conn_t style,
|
||||
uint_t tunneldepth);
|
||||
bool sam_close(sam_sess_t *session);
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost,
|
||||
unsigned short samport, const char *destname, sam_conn_t style,
|
||||
unsigned int tunneldepth);
|
||||
/* SAM controls - utilities */
|
||||
extern void sam_naming_lookup(sam_sess_t *session, const char *name);
|
||||
extern bool sam_read_buffer(sam_sess_t *session);
|
||||
extern const char *sam_strerror(samerr_t code);
|
||||
void sam_naming_lookup(sam_sess_t *session, const char *name);
|
||||
bool sam_read_buffer(sam_sess_t *session);
|
||||
const char *sam_strerror(samerr_t code);
|
||||
/* SAM controls - callbacks */
|
||||
extern void (*sam_diedback)(sam_sess_t *session);
|
||||
extern void (*sam_logback)(char *str);
|
||||
extern void (*sam_namingback)(char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result);
|
||||
void (*sam_diedback)(sam_sess_t *session);
|
||||
void (*sam_logback)(char *str);
|
||||
void (*sam_namingback)(sam_sess_t *session, char *name,
|
||||
sam_pubkey_t pubkey, samerr_t result);
|
||||
|
||||
/* Stream commands */
|
||||
extern void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id);
|
||||
extern sam_sid_t sam_stream_connect(sam_sess_t *session,
|
||||
const sam_pubkey_t dest);
|
||||
extern samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
|
||||
const void *data, size_t size);
|
||||
void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id);
|
||||
sam_sid_t sam_stream_connect(sam_sess_t *session, const sam_pubkey_t dest);
|
||||
samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
|
||||
const void *data, size_t size);
|
||||
/* Stream commands - callbacks */
|
||||
extern void (*sam_closeback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t reason);
|
||||
extern void (*sam_connectback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_pubkey_t dest);
|
||||
extern void (*sam_databack)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
void *data, size_t size);
|
||||
extern void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t result);
|
||||
void (*sam_closeback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t reason);
|
||||
void (*sam_connectback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_pubkey_t dest);
|
||||
void (*sam_databack)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
void *data, size_t size);
|
||||
void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t result);
|
||||
|
||||
/* Stream send queue (experimental) */
|
||||
extern void sam_sendq_add(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_sendq_t **sendq, const void *data, size_t dsize);
|
||||
extern void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_sendq_t **sendq);
|
||||
void sam_sendq_add(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_sendq_t **sendq, const void *data, size_t dsize);
|
||||
void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_sendq_t **sendq);
|
||||
|
||||
/* Datagram commands */
|
||||
extern samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
|
||||
const void *data, size_t size);
|
||||
|
||||
samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
|
||||
const void *data, size_t size);
|
||||
/* Datagram commands - callbacks */
|
||||
extern void (*sam_dgramback)(sam_sess_t *session, sam_pubkey_t dest,
|
||||
void *data, size_t size);
|
||||
void (*sam_dgramback)(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
size_t size);
|
||||
|
||||
/* Raw commands */
|
||||
samerr_t sam_raw_send(sam_sess_t *session, const sam_pubkey_t dest,
|
||||
const void *data, size_t size);
|
||||
/* Raw commands - callbacks */
|
||||
void (*sam_rawback)(sam_sess_t *session, void *data, size_t size);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SAM_H */
|
||||
#endif /* LIBSAM_SAM_H */
|
||||
|
@ -33,8 +33,8 @@
|
||||
* snprintf.c)
|
||||
*/
|
||||
|
||||
#ifndef SNPRINTF_H
|
||||
#define SNPRINTF_H
|
||||
#ifndef LIBSAM_SNPRINTF_H
|
||||
#define LIBSAM_SNPRINTF_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -46,4 +46,4 @@ int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SNPRINTF_H */
|
||||
#endif /* LIBSAM_SNPRINTF_H */
|
||||
|
@ -32,8 +32,8 @@
|
||||
* Note: The strl.c file retains its original license (at the top of strl.c)
|
||||
*/
|
||||
|
||||
#ifndef STRL_H
|
||||
#define STRL_H
|
||||
#ifndef LIBSAM_STRL_H
|
||||
#define LIBSAM_STRL_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -44,4 +44,4 @@ extern size_t strlcpy(char *dst, const char *src, size_t siz);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* STRL_H */
|
||||
#endif /* LIBSAM_STRL_H */
|
||||
|
@ -28,8 +28,8 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
#include "sam.h"
|
||||
#include "platform.h"
|
||||
|
||||
static bool sam_hello(sam_sess_t *session);
|
||||
static void sam_log(const char *format, ...);
|
||||
@ -40,9 +40,9 @@ static bool sam_readable(sam_sess_t *session);
|
||||
static sam_sendq_t *sam_sendq_create();
|
||||
static samerr_t sam_session_create(sam_sess_t *session,
|
||||
const char *destname, sam_conn_t style,
|
||||
uint_t tunneldepth);
|
||||
uint tunneldepth);
|
||||
static bool sam_socket_connect(sam_sess_t *session, const char *host,
|
||||
uint16_t port);
|
||||
ushort port);
|
||||
static bool sam_socket_resolve(const char *hostname, char *ipaddr);
|
||||
#ifdef WINSOCK
|
||||
static samerr_t sam_winsock_cleanup();
|
||||
@ -55,28 +55,41 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n);
|
||||
* Callback functions
|
||||
* Note: if you add a new callback be sure to check for non-NULL in sam_connect
|
||||
*/
|
||||
|
||||
/* a peer closed the connection */
|
||||
void (*sam_closeback)(sam_sess_t *session, sam_sid_t stream_id, samerr_t reason)
|
||||
= NULL;
|
||||
|
||||
/* a peer connected to us */
|
||||
void (*sam_connectback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
sam_pubkey_t dest) = NULL;
|
||||
|
||||
/* a peer sent some stream data (`data' MUST be freed) */
|
||||
void (*sam_databack)(sam_sess_t *session, sam_sid_t stream_id, void *data,
|
||||
size_t size) = NULL;
|
||||
|
||||
/* a peer sent some datagram data (`data' MUST be freed) */
|
||||
void (*sam_dgramback)(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
size_t size) = NULL;
|
||||
|
||||
/* we lost the connection to the SAM host */
|
||||
void (*sam_diedback)(sam_sess_t *session) = NULL;
|
||||
|
||||
/* logging callback */
|
||||
void (*sam_logback)(char *str) = NULL;
|
||||
|
||||
/* naming lookup reply - `pubkey' will be NULL if `result' isn't SAM_OK */
|
||||
void (*sam_namingback)(char *name, sam_pubkey_t pubkey, samerr_t result) = NULL;
|
||||
void (*sam_namingback)(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result) = NULL;
|
||||
|
||||
/* our connection to a peer has completed */
|
||||
void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t result) = NULL;
|
||||
|
||||
/* a peer sent some raw data (`data' MUST be freed) */
|
||||
void (*sam_rawback)(sam_sess_t *session, void *data, size_t size) = NULL;
|
||||
|
||||
|
||||
/*
|
||||
* Closes the connection to the SAM host
|
||||
*
|
||||
@ -85,8 +98,12 @@ void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
bool sam_close(sam_sess_t *session)
|
||||
{
|
||||
assert(session != NULL);
|
||||
if (!session->connected)
|
||||
if (!session->connected) {
|
||||
#if SAM_WIRETAP
|
||||
SAMLOGS("Connection already closed - sam_close() skipped");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef WINSOCK
|
||||
if (closesocket(session->sock) == SOCKET_ERROR) {
|
||||
@ -95,13 +112,19 @@ bool sam_close(sam_sess_t *session)
|
||||
return false;
|
||||
}
|
||||
session->connected = false;
|
||||
if (sam_winsock_cleanup() == SAM_OK)
|
||||
if (sam_winsock_cleanup() == SAM_OK) {
|
||||
#if SAM_WIRETAP
|
||||
SAMLOGS("Connection closed safely");
|
||||
#endif
|
||||
return true;
|
||||
else
|
||||
} else
|
||||
return false;
|
||||
#else
|
||||
if (close(session->sock) == 0) {
|
||||
session->connected = false;
|
||||
#if SAM_WIRETAP
|
||||
SAMLOGS("Connection closed safely");
|
||||
#endif
|
||||
return true;
|
||||
} else {
|
||||
SAMLOG("Failed closing the SAM connection (%s)", strerror(errno));
|
||||
@ -122,11 +145,10 @@ bool sam_close(sam_sess_t *session)
|
||||
* tunneldepth - length of the I2P tunnels created by this program (longer is
|
||||
* more anonymous, but slower)
|
||||
*
|
||||
* Returns: True on success, false on failure. If true, `session' will be ready
|
||||
* for use.
|
||||
* Returns: SAM error code. If SAM_OK, `session' will be ready for use.
|
||||
*/
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
|
||||
const char *destname, sam_conn_t style, uint_t tunneldepth)
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost, ushort samport,
|
||||
const char *destname, sam_conn_t style, uint tunneldepth)
|
||||
{
|
||||
assert(session != NULL);
|
||||
samerr_t rc;
|
||||
@ -146,7 +168,11 @@ samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
|
||||
return SAM_CALLBACKS_UNSET;
|
||||
}
|
||||
} else if (style == SAM_RAW) {
|
||||
abort(); /* not implemented yet */
|
||||
if (sam_diedback == NULL || sam_logback == NULL
|
||||
|| sam_namingback == NULL || sam_rawback == NULL) {
|
||||
SAMLOGS("Please set callback functions before connecting");
|
||||
return SAM_CALLBACKS_UNSET;
|
||||
}
|
||||
} else {
|
||||
SAMLOGS("Unknown connection style");
|
||||
return SAM_BAD_STYLE;
|
||||
@ -188,7 +214,7 @@ samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
|
||||
* data - the data we're sending
|
||||
* size - the size of the data
|
||||
*
|
||||
* Returns: true on success, false on failure
|
||||
* Returns: SAM_OK on success
|
||||
*/
|
||||
samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
|
||||
const void *data, size_t size)
|
||||
@ -286,6 +312,7 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
#define SAM_NAMING_REPLY_OK "NAMING REPLY RESULT=OK"
|
||||
#define SAM_NAMING_REPLY_IK "NAMING REPLY RESULT=INVALID_KEY"
|
||||
#define SAM_NAMING_REPLY_KNF "NAMING REPLY RESULT=KEY_NOT_FOUND"
|
||||
#define SAM_RAW_RECEIVED_REPLY "RAW RECEIVED"
|
||||
#define SAM_STREAM_CLOSED_REPLY "STREAM CLOSED"
|
||||
#define SAM_STREAM_CONNECTED_REPLY "STREAM CONNECTED"
|
||||
#define SAM_STREAM_RECEIVED_REPLY "STREAM RECEIVED"
|
||||
@ -296,6 +323,10 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
#define SAM_STREAM_STATUS_REPLY_IK "STREAM STATUS RESULT=INVALID_KEY"
|
||||
#define SAM_STREAM_STATUS_REPLY_TO "STREAM STATUS RESULT=TIMEOUT"
|
||||
|
||||
/*
|
||||
* TODO: add raw parsing
|
||||
*/
|
||||
|
||||
if (strncmp(s, SAM_DGRAM_RECEIVED_REPLY,
|
||||
strlen(SAM_DGRAM_RECEIVED_REPLY)) == 0) {
|
||||
char *p;
|
||||
@ -356,7 +387,7 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
q++;
|
||||
strlcpy(name, p, sizeof name);
|
||||
strlcpy(pubkey, q, sizeof pubkey);
|
||||
sam_namingback(name, pubkey, SAM_OK);
|
||||
sam_namingback(session, name, pubkey, SAM_OK);
|
||||
|
||||
} else if (strncmp(s, SAM_NAMING_REPLY_IK,
|
||||
strlen(SAM_NAMING_REPLY_IK)) == 0) {
|
||||
@ -364,7 +395,7 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
if (q != NULL)
|
||||
*q = '\0';
|
||||
strlcpy(name, p, sizeof name);
|
||||
sam_namingback(name, NULL, SAM_INVALID_KEY);
|
||||
sam_namingback(session, name, NULL, SAM_INVALID_KEY);
|
||||
|
||||
} else if (strncmp(s, SAM_NAMING_REPLY_KNF,
|
||||
strlen(SAM_NAMING_REPLY_KNF)) == 0) {
|
||||
@ -372,14 +403,14 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
if (q != NULL)
|
||||
*q = '\0';
|
||||
strlcpy(name, p, sizeof name);
|
||||
sam_namingback(name, NULL, SAM_KEY_NOT_FOUND);
|
||||
sam_namingback(session, name, NULL, SAM_KEY_NOT_FOUND);
|
||||
|
||||
} else {
|
||||
q = strchr(p, ' '); /* ' 'MES.. (optional) */
|
||||
if (q != NULL)
|
||||
*q = '\0';
|
||||
strlcpy(name, p, sizeof name);
|
||||
sam_namingback(name, NULL, SAM_UNKNOWN);
|
||||
sam_namingback(session, name, NULL, SAM_UNKNOWN);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -509,6 +540,42 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends data to a destination in a raw packet
|
||||
*
|
||||
* dest - base 64 destination of who we're sending to
|
||||
* data - the data we're sending
|
||||
* size - the size of the data
|
||||
*
|
||||
* Returns: SAM_OK on success
|
||||
*/
|
||||
samerr_t sam_raw_send(sam_sess_t *session, const sam_pubkey_t dest,
|
||||
const void *data, size_t size)
|
||||
{
|
||||
assert(session != NULL);
|
||||
char cmd[SAM_PKCMD_LEN];
|
||||
|
||||
if (size < 1 || size > SAM_RAW_PAYLOAD_MAX) {
|
||||
#ifdef NO_Z_FORMAT
|
||||
SAMLOG("Invalid data send size (%u bytes)", size);
|
||||
#else
|
||||
SAMLOG("Invalid data send size (%zu bytes)", size);
|
||||
#endif
|
||||
return SAM_TOO_BIG;
|
||||
}
|
||||
#ifdef NO_Z_FORMAT
|
||||
snprintf(cmd, sizeof cmd, "RAW SEND DESTINATION=%s SIZE=%u\n",
|
||||
dest, size);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "RAW SEND DESTINATION=%s SIZE=%zu\n",
|
||||
dest, size);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
sam_write(session, data, size);
|
||||
|
||||
return SAM_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads and callbacks everything in the SAM network buffer until it is clear
|
||||
*
|
||||
@ -589,7 +656,7 @@ static ssize_t sam_read1(sam_sess_t *session, char *buf, size_t n)
|
||||
if (*p == '\n') { /* end of SAM response */
|
||||
*p = '\0';
|
||||
#if SAM_WIRETAP
|
||||
printf("<<<< %s\n", buf);
|
||||
printf("*RR* %s\n", buf);
|
||||
#endif
|
||||
return n - nleft;
|
||||
}
|
||||
@ -654,7 +721,16 @@ static ssize_t sam_read2(sam_sess_t *session, void *buf, size_t n)
|
||||
p += nread;
|
||||
}
|
||||
#if SAM_WIRETAP
|
||||
printf("<<<< (read2() %d bytes)\n", n);
|
||||
p = buf;
|
||||
printf("*RR* ");
|
||||
for (size_t x = 0; x < n; x++) {
|
||||
if (isprint(((byte*)p)[x]))
|
||||
printf("%c,", ((byte*)p)[x]);
|
||||
else
|
||||
printf("%03d,", ((byte*)p)[x]);
|
||||
}
|
||||
printf("\n");
|
||||
printf("*RR* (read2() read %d bytes)\n", n);
|
||||
#endif
|
||||
assert(nleft == 0);/* <---\ */
|
||||
return n - nleft; /* should be equal to initial n */
|
||||
@ -680,7 +756,7 @@ static bool sam_readable(sam_sess_t *session)
|
||||
FD_ZERO(&rset);
|
||||
FD_SET(session->sock, &rset);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10;
|
||||
tv.tv_usec = 0;
|
||||
rc = select(session->sock + 1, &rset, NULL, NULL, &tv);
|
||||
if (rc == 0)
|
||||
return false;
|
||||
@ -791,39 +867,6 @@ void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocates memory for the session and sets its default values
|
||||
*
|
||||
* session - pointer to a previously allocated sam_sess_t to initalise, or NULL
|
||||
* if you want memory to be allocated by this function
|
||||
*/
|
||||
sam_sess_t *sam_session_init(sam_sess_t *session)
|
||||
{
|
||||
if (session == NULL) {
|
||||
session = malloc(sizeof(sam_sess_t));
|
||||
if (session == NULL) {
|
||||
SAMLOGS("Out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
session->connected = false;
|
||||
session->prev_id = 0;
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees memory used by the session and sets the pointer to NULL
|
||||
*
|
||||
* session - pointer to a pointer to a sam_sess_t
|
||||
*/
|
||||
void sam_session_free(sam_sess_t **session)
|
||||
{
|
||||
assert(*session != NULL);
|
||||
free(*session);
|
||||
*session = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends the second SAM handshake command and checks the reply
|
||||
*
|
||||
@ -835,7 +878,7 @@ void sam_session_free(sam_sess_t **session)
|
||||
* Returns: SAM error code
|
||||
*/
|
||||
static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
|
||||
sam_conn_t style, uint_t tunneldepth)
|
||||
sam_conn_t style, uint tunneldepth)
|
||||
{
|
||||
assert(session != NULL);
|
||||
#define SAM_SESSTATUS_REPLY_OK "SESSION STATUS RESULT=OK"
|
||||
@ -880,6 +923,40 @@ static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
|
||||
return SAM_UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocates memory for the session and sets its default values
|
||||
*
|
||||
* session - pointer to a previously allocated sam_sess_t to initalise, or NULL
|
||||
* if you want memory to be allocated by this function
|
||||
*/
|
||||
sam_sess_t *sam_session_init(sam_sess_t *session)
|
||||
{
|
||||
if (session == NULL) {
|
||||
session = malloc(sizeof(sam_sess_t));
|
||||
if (session == NULL) {
|
||||
SAMLOGS("Out of memory");
|
||||
abort();
|
||||
}
|
||||
session->child = NULL;
|
||||
}
|
||||
session->connected = false;
|
||||
session->prev_id = 0;
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees memory used by the session and sets the pointer to NULL
|
||||
*
|
||||
* session - pointer to a pointer to a sam_sess_t
|
||||
*/
|
||||
void sam_session_free(sam_sess_t **session)
|
||||
{
|
||||
assert(*session != NULL);
|
||||
free(*session);
|
||||
*session = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connects to a remote host and returns a connected descriptor
|
||||
*
|
||||
@ -888,7 +965,7 @@ static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
|
||||
*
|
||||
* Returns: true on sucess, false on error, with errno set
|
||||
*/
|
||||
bool sam_socket_connect(sam_sess_t *session, const char *host, uint16_t port)
|
||||
bool sam_socket_connect(sam_sess_t *session, const char *host, ushort port)
|
||||
{
|
||||
assert(session != NULL);
|
||||
struct sockaddr_in hostaddr;
|
||||
@ -1005,11 +1082,7 @@ void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id)
|
||||
assert(session != NULL);
|
||||
char cmd[SAM_CMD_LEN];
|
||||
|
||||
#ifdef FAST32_IS_LONG
|
||||
snprintf(cmd, sizeof cmd, "STREAM CLOSE ID=%ld\n", stream_id);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM CLOSE ID=%d\n", stream_id);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
|
||||
return;
|
||||
@ -1028,13 +1101,8 @@ sam_sid_t sam_stream_connect(sam_sess_t *session, const sam_pubkey_t dest)
|
||||
char cmd[SAM_PKCMD_LEN];
|
||||
|
||||
session->prev_id++; /* increment the id for the connection */
|
||||
#ifdef FAST32_IS_LONG
|
||||
snprintf(cmd, sizeof cmd, "STREAM CONNECT ID=%ld DESTINATION=%s\n",
|
||||
session->prev_id, dest);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM CONNECT ID=%d DESTINATION=%s\n",
|
||||
session->prev_id, dest);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
|
||||
return session->prev_id;
|
||||
@ -1066,15 +1134,9 @@ samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
|
||||
return SAM_TOO_BIG;
|
||||
}
|
||||
#ifdef NO_Z_FORMAT
|
||||
#ifdef FAST32_IS_LONG
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%u\n",
|
||||
stream_id, size);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%d SIZE=%u\n",
|
||||
stream_id, size);
|
||||
#endif
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%u\n", stream_id, size);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%d SIZE=%zu\n",
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%zu\n",
|
||||
stream_id, size);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
@ -1120,7 +1182,7 @@ const char *sam_strerror(samerr_t code)
|
||||
case SAM_BAD_STYLE: /* Style must be stream, datagram, or raw */
|
||||
return "Bad connection style";
|
||||
case SAM_BAD_VERSION: /* sam_hello() had an unexpected reply */
|
||||
return "Bad SAM version";
|
||||
return "Not a SAM port, or bad SAM version";
|
||||
case SAM_CALLBACKS_UNSET: /* Some callbacks are still set to NULL */
|
||||
return "Callbacks unset";
|
||||
case SAM_SOCKET_ERROR: /* TCP/IP connection to the SAM host:port
|
||||
@ -1324,7 +1386,8 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
|
||||
return -1;
|
||||
}
|
||||
#if SAM_WIRETAP
|
||||
const uchar_t *cp = buf;
|
||||
const byte *cp = buf;
|
||||
printf("*WW* ");
|
||||
for (size_t x = 0; x < n; x++) {
|
||||
if (isprint(cp[x]))
|
||||
printf("%c,", cp[x]);
|
||||
@ -1356,7 +1419,7 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
|
||||
p += nwritten;
|
||||
}
|
||||
#if SAM_WIRETAP
|
||||
printf(">>>> (write() %d bytes)\n", n);
|
||||
printf("*WW* (write() wrote %d bytes)\n", n);
|
||||
#endif
|
||||
|
||||
return n;
|
||||
|
@ -10,7 +10,7 @@
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
srcdir="./src:./test"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
|
@ -46,7 +46,7 @@ public class SAMBridge implements Runnable {
|
||||
* app designated destination name to the base64 of the I2P formatted
|
||||
* destination keys (Destination+PrivateKey+SigningPrivateKey)
|
||||
*/
|
||||
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
|
||||
private Map nameToPrivKeys;
|
||||
|
||||
private boolean acceptConnections = true;
|
||||
|
||||
@ -65,6 +65,7 @@ public class SAMBridge implements Runnable {
|
||||
*/
|
||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
|
||||
persistFilename = persistFile;
|
||||
nameToPrivKeys = new HashMap(8);
|
||||
loadKeys();
|
||||
try {
|
||||
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
|
||||
@ -93,16 +94,18 @@ public class SAMBridge implements Runnable {
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
*/
|
||||
public Destination getDestination(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
synchronized (nameToPrivKeys) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,9 +117,11 @@ public class SAMBridge implements Runnable {
|
||||
* @return null if the name does not exist, else the stream
|
||||
*/
|
||||
public String getKeystream(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
synchronized (nameToPrivKeys) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,7 +129,9 @@ public class SAMBridge implements Runnable {
|
||||
*
|
||||
*/
|
||||
public void addKeystream(String name, String stream) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
synchronized (nameToPrivKeys) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
}
|
||||
storeKeys();
|
||||
}
|
||||
|
||||
@ -132,49 +139,52 @@ public class SAMBridge implements Runnable {
|
||||
* Load up the keys from the persistFilename
|
||||
*
|
||||
*/
|
||||
private synchronized void loadKeys() {
|
||||
Map keys = new HashMap(16);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(persistFilename);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
keys.put(name, privKeys);
|
||||
private void loadKeys() {
|
||||
synchronized (nameToPrivKeys) {
|
||||
nameToPrivKeys.clear();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(persistFilename);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
nameToPrivKeys.put(name, privKeys);
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
nameToPrivKeys = Collections.synchronizedMap(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current keys to disk in the location specified on creation
|
||||
*
|
||||
*/
|
||||
private synchronized void storeKeys() {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String privKeys = (String)nameToPrivKeys.get(name);
|
||||
out.write(name.getBytes());
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes());
|
||||
out.write('\n');
|
||||
private void storeKeys() {
|
||||
synchronized (nameToPrivKeys) {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String privKeys = (String)nameToPrivKeys.get(name);
|
||||
out.write(name.getBytes());
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +220,15 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
|
||||
I2PThread t = new I2PThread(bridge, "SAMListener");
|
||||
if (Boolean.valueOf(System.getProperty("sam.shutdownOnOOM", "false")).booleanValue()) {
|
||||
t.addOOMEventListener(new I2PThread.OOMEventListener() {
|
||||
public void outOfMemory(OutOfMemoryError err) {
|
||||
err.printStackTrace();
|
||||
System.err.println("OOMed, die die die");
|
||||
System.exit(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
t.start();
|
||||
}
|
||||
|
||||
@ -240,6 +259,7 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (serverSocket == null) return;
|
||||
try {
|
||||
while (acceptConnections) {
|
||||
Socket s = serverSocket.accept();
|
||||
@ -270,8 +290,11 @@ public class SAMBridge implements Runnable {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM Error sending error reply", ioe);
|
||||
}
|
||||
s.close();
|
||||
}
|
||||
try { s.close(); } catch (IOException ioe) {}
|
||||
} catch (Exception ee) {
|
||||
try { s.close(); } catch (IOException ioe) {}
|
||||
_log.log(Log.CRIT, "Unexpected error handling SAM connection", ee);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@ -280,7 +303,8 @@ public class SAMBridge implements Runnable {
|
||||
try {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Shutting down, closing server socket");
|
||||
serverSocket.close();
|
||||
if (serverSocket != null)
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,14 @@ public abstract class SAMHandler implements Runnable {
|
||||
socketOS.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If you're crazy enough to write to the raw socket, grab the write lock
|
||||
* with getWriteLock(), synchronize against it, and write to the getOut()
|
||||
*
|
||||
*/
|
||||
protected Object getWriteLock() { return socketWLock; }
|
||||
protected OutputStream getOut() { return socketOS; }
|
||||
|
||||
/**
|
||||
* Write a string to the handler's socket. This method must
|
||||
@ -111,6 +119,8 @@ public abstract class SAMHandler implements Runnable {
|
||||
* @return True is the string was successfully written, false otherwise
|
||||
*/
|
||||
protected final boolean writeString(String str) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending the client: [" + str + "]");
|
||||
try {
|
||||
writeBytes(str.getBytes("ISO-8859-1"));
|
||||
} catch (IOException e) {
|
||||
|
@ -17,6 +17,7 @@ import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -36,14 +37,11 @@ public class SAMHandlerFactory {
|
||||
* @return A SAM protocol handler, or null if the client closed before the handshake
|
||||
*/
|
||||
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
|
||||
BufferedReader br;
|
||||
String line;
|
||||
StringTokenizer tok;
|
||||
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(s.getInputStream(),
|
||||
"ISO-8859-1"));
|
||||
line = br.readLine();
|
||||
line = DataHelper.readLine(s.getInputStream());
|
||||
if (line == null) {
|
||||
_log.debug("Connection closed by client");
|
||||
return null;
|
||||
@ -152,6 +150,8 @@ public class SAMHandlerFactory {
|
||||
|
||||
/* Get the major protocol version from a string */
|
||||
private static int getMajor(String ver) {
|
||||
if ( (ver == null) || (ver.indexOf('.') < 0) )
|
||||
return -1;
|
||||
try {
|
||||
String major = ver.substring(0, ver.indexOf("."));
|
||||
return Integer.parseInt(major);
|
||||
@ -164,6 +164,8 @@ public class SAMHandlerFactory {
|
||||
|
||||
/* Get the minor protocol version from a string */
|
||||
private static int getMinor(String ver) {
|
||||
if ( (ver == null) || (ver.indexOf('.') < 0) )
|
||||
return -1;
|
||||
try {
|
||||
String major = ver.substring(ver.indexOf(".") + 1);
|
||||
return Integer.parseInt(major);
|
||||
|
@ -15,8 +15,10 @@ import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
@ -28,8 +30,11 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -51,7 +56,10 @@ public class SAMStreamSession {
|
||||
private I2PSocketManager socketMgr = null;
|
||||
|
||||
private Object handlersMapLock = new Object();
|
||||
/** stream id (Long) to SAMStreamSessionSocketReader */
|
||||
private HashMap handlersMap = new HashMap();
|
||||
/** stream id (Long) to StreamSender */
|
||||
private HashMap sendersMap = new HashMap();
|
||||
|
||||
private Object idLock = new Object();
|
||||
private int lastNegativeId = 0;
|
||||
@ -59,6 +67,14 @@ public class SAMStreamSession {
|
||||
// Can we create outgoing connections?
|
||||
private boolean canCreate = false;
|
||||
|
||||
/**
|
||||
* should we flush every time we get a STREAM SEND, or leave that up to
|
||||
* the streaming lib to decide?
|
||||
*/
|
||||
private boolean forceFlush = false;
|
||||
public static String PROP_FORCE_FLUSH = "sam.forceFlush";
|
||||
public static String DEFAULT_FORCE_FLUSH = "false";
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session.
|
||||
*
|
||||
@ -107,9 +123,6 @@ public class SAMStreamSession {
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new SAMException("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
// streams MUST be mode=guaranteed (though i think the socket manager
|
||||
// enforces this anyway...
|
||||
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
|
||||
_log.debug("Creating I2PSocketManager...");
|
||||
socketMgr = I2PSocketManagerFactory.createManager(destStream,
|
||||
@ -120,6 +133,8 @@ public class SAMStreamSession {
|
||||
throw new SAMException("Error creating I2PSocketManager");
|
||||
}
|
||||
|
||||
forceFlush = Boolean.valueOf(allprops.getProperty(PROP_FORCE_FLUSH, DEFAULT_FORCE_FLUSH)).booleanValue();
|
||||
|
||||
boolean canReceive = false;
|
||||
if (dir.equals("BOTH")) {
|
||||
canCreate = true;
|
||||
@ -180,9 +195,9 @@ public class SAMStreamSession {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(dest);
|
||||
|
||||
// FIXME: we should config I2PSocketOptions here
|
||||
I2PSocketOptions opts = new I2PSocketOptions();
|
||||
opts.setConnectTimeout(60 * 1000);
|
||||
I2PSocketOptions opts = socketMgr.buildOptions(props);
|
||||
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
|
||||
opts.setConnectTimeout(60 * 1000);
|
||||
|
||||
_log.debug("Connecting new I2PSocket...");
|
||||
I2PSocket i2ps = socketMgr.connect(d, opts);
|
||||
@ -197,18 +212,25 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param data Bytes to be sent
|
||||
*
|
||||
* @return True if the data was sent, false otherwise
|
||||
* @return True if the data was queued for sending, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(int id, byte[] data) {
|
||||
Destination d = new Destination();
|
||||
SAMStreamSessionSocketHandler handler = getSocketHandler(id);
|
||||
public boolean sendBytes(int id, InputStream in, int size) throws IOException {
|
||||
StreamSender sender = getSender(id);
|
||||
|
||||
if (handler == null) {
|
||||
_log.error("Trying to send bytes through inexistent handler " +id);
|
||||
if (sender == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Trying to send bytes through nonexistent handler " +id);
|
||||
// even though it failed, we need to read those bytes!
|
||||
for (int i = 0; i < size; i++) {
|
||||
int c = in.read();
|
||||
if (c == -1)
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler.sendBytes(data);
|
||||
sender.sendBytes(in, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,13 +270,15 @@ public class SAMStreamSession {
|
||||
* @return An id associated to the socket handler
|
||||
*/
|
||||
private int createSocketHandler(I2PSocket s, int id) {
|
||||
SAMStreamSessionSocketHandler handler;
|
||||
SAMStreamSessionSocketReader reader = null;
|
||||
StreamSender sender = null;
|
||||
if (id == 0) {
|
||||
id = createUniqueId();
|
||||
}
|
||||
|
||||
try {
|
||||
handler = new SAMStreamSessionSocketHandler(s, id);
|
||||
reader = new SAMStreamSessionSocketReader(s, id);
|
||||
sender = new StreamSender(s, id);
|
||||
} catch (IOException e) {
|
||||
_log.error("IOException when creating SAM STREAM session socket handler", e);
|
||||
recv.stopStreamReceiving();
|
||||
@ -262,10 +286,13 @@ public class SAMStreamSession {
|
||||
}
|
||||
|
||||
synchronized (handlersMapLock) {
|
||||
handlersMap.put(new Integer(id), handler);
|
||||
handlersMap.put(new Integer(id), reader);
|
||||
sendersMap.put(new Integer(id), sender);
|
||||
}
|
||||
|
||||
I2PThread t = new I2PThread(handler, "SAMStreamSessionSocketHandler");
|
||||
I2PThread t = new I2PThread(reader, "SAMReader" + id);
|
||||
t.start();
|
||||
t = new I2PThread(sender, "SAMSender" + id);
|
||||
t.start();
|
||||
|
||||
return id;
|
||||
@ -283,9 +310,14 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param id Handler id
|
||||
*/
|
||||
private SAMStreamSessionSocketHandler getSocketHandler(int id) {
|
||||
private SAMStreamSessionSocketReader getSocketReader(int id) {
|
||||
synchronized (handlersMapLock) {
|
||||
return (SAMStreamSessionSocketHandler)handlersMap.get(new Integer(id));
|
||||
return (SAMStreamSessionSocketReader)handlersMap.get(new Integer(id));
|
||||
}
|
||||
}
|
||||
private StreamSender getSender(int id) {
|
||||
synchronized (handlersMapLock) {
|
||||
return (StreamSender)sendersMap.get(new Integer(id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,19 +338,19 @@ public class SAMStreamSession {
|
||||
* @param id Handler id to be removed
|
||||
*/
|
||||
private void removeSocketHandler(int id) {
|
||||
SAMStreamSessionSocketHandler removed;
|
||||
SAMStreamSessionSocketReader reader = null;
|
||||
StreamSender sender = null;
|
||||
|
||||
synchronized (handlersMapLock) {
|
||||
removed = (SAMStreamSessionSocketHandler)handlersMap.remove(new Integer(id));
|
||||
reader = (SAMStreamSessionSocketReader)handlersMap.remove(new Integer(id));
|
||||
sender = (StreamSender)sendersMap.remove(new Integer(id));
|
||||
}
|
||||
|
||||
if (removed == null) {
|
||||
_log.error("BUG! Trying to remove inexistent SAM STREAM session socket handler " + id);
|
||||
recv.stopStreamReceiving();
|
||||
} else {
|
||||
removed.stopRunning();
|
||||
_log.debug("Removed SAM STREAM session socket handler " + id);
|
||||
}
|
||||
if (reader != null)
|
||||
reader.stopRunning();
|
||||
if (sender != null)
|
||||
sender.stopRunning();
|
||||
_log.debug("Removed SAM STREAM session socket handler " + id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,9 +369,11 @@ public class SAMStreamSession {
|
||||
|
||||
while (iter.hasNext()) {
|
||||
id = (Integer)iter.next();
|
||||
((SAMStreamSessionSocketHandler)handlersMap.get(id)).stopRunning();
|
||||
((SAMStreamSessionSocketReader)handlersMap.get(id)).stopRunning();
|
||||
((StreamSender)sendersMap.get(id)).stopRunning();
|
||||
}
|
||||
handlersMap.clear();
|
||||
sendersMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,6 +425,8 @@ public class SAMStreamSession {
|
||||
while (stillRunning) {
|
||||
try {
|
||||
i2ps = serverSocket.accept();
|
||||
if (i2ps == null)
|
||||
break;
|
||||
|
||||
_log.debug("New incoming connection");
|
||||
|
||||
@ -417,6 +453,8 @@ public class SAMStreamSession {
|
||||
} catch (I2PException e) {
|
||||
_log.debug("Caught I2PException", e);
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
_log.debug("Shutting down SAM STREAM session server");
|
||||
}
|
||||
@ -429,10 +467,9 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
public class SAMStreamSessionSocketHandler implements Runnable {
|
||||
public class SAMStreamSessionSocketReader implements Runnable {
|
||||
|
||||
private I2PSocket i2pSocket = null;
|
||||
private OutputStream i2pSocketOS = null;
|
||||
|
||||
private Object runningLock = new Object();
|
||||
private boolean stillRunning = true;
|
||||
@ -440,43 +477,20 @@ public class SAMStreamSession {
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session socket handler
|
||||
* Create a new SAM STREAM session socket reader
|
||||
*
|
||||
* @param s Socket to be handled
|
||||
* @param id Unique id assigned to the handler
|
||||
*/
|
||||
public SAMStreamSessionSocketHandler(I2PSocket s, int id) throws IOException {
|
||||
public SAMStreamSessionSocketReader(I2PSocket s, int id) throws IOException {
|
||||
_log.debug("Instantiating new SAM STREAM session socket handler");
|
||||
|
||||
i2pSocket = s;
|
||||
i2pSocketOS = s.getOutputStream();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send bytes through the SAM STREAM session socket handler
|
||||
*
|
||||
* @param data Data to be sent
|
||||
*
|
||||
* @return True if data has been sent without errors, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(byte[] data) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Handler " + id + ": sending " + data.length
|
||||
+ " bytes");
|
||||
}
|
||||
try {
|
||||
i2pSocketOS.write(data);
|
||||
} catch (IOException e) {
|
||||
_log.error("Error sending data through I2P socket", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket handler
|
||||
* Stop a SAM STREAM session socket reader
|
||||
*
|
||||
*/
|
||||
public void stopRunning() {
|
||||
@ -535,4 +549,99 @@ public class SAMStreamSession {
|
||||
_log.debug("Shutting down SAM STREAM session socket handler " +id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lets us push data through the stream without blocking, (even after exceeding
|
||||
* the I2PSocket's buffer)
|
||||
*/
|
||||
private class StreamSender implements Runnable {
|
||||
private List _data;
|
||||
private int _id;
|
||||
private ByteCache _cache;
|
||||
private OutputStream _out = null;
|
||||
private boolean _stillRunning;
|
||||
|
||||
public StreamSender(I2PSocket s, int id) throws IOException {
|
||||
_data = new ArrayList(1);
|
||||
_id = id;
|
||||
_cache = ByteCache.getInstance(4, 32*1024);
|
||||
_out = s.getOutputStream();
|
||||
_stillRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send bytes through the SAM STREAM session socket sender
|
||||
*
|
||||
* @param data Data to be sent
|
||||
*
|
||||
* @throws IOException if the client didnt provide enough data
|
||||
*/
|
||||
public void sendBytes(InputStream in, int size) throws IOException {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handler " + _id + ": sending " + size + " bytes");
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
int read = DataHelper.read(in, ba.getData(), 0, size);
|
||||
if (read != size)
|
||||
throw new IOException("Insufficient data from the SAM client (" + read + "/" + size + ")");
|
||||
|
||||
ba.setValid(read);
|
||||
synchronized (_data) {
|
||||
_data.add(ba);
|
||||
_data.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket sender
|
||||
*
|
||||
*/
|
||||
public void stopRunning() {
|
||||
_log.debug("stopRunning() invoked on socket sender " + _id);
|
||||
_stillRunning = false;
|
||||
synchronized (_data) {
|
||||
_data.clear();
|
||||
_data.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
ByteArray data = null;
|
||||
while (_stillRunning) {
|
||||
data = null;
|
||||
try {
|
||||
synchronized (_data) {
|
||||
if (_data.size() > 0)
|
||||
data = (ByteArray)_data.remove(0);
|
||||
else
|
||||
_data.wait(5000);
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
try {
|
||||
_out.write(data.getData(), 0, data.getValid());
|
||||
if (forceFlush) {
|
||||
// i dont like doing this, but it clears the buffer issues
|
||||
_out.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// ok, the stream failed, but the SAM client didn't
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Stream failed", ioe);
|
||||
|
||||
removeSocketHandler(_id);
|
||||
stopRunning();
|
||||
|
||||
} finally {
|
||||
_cache.release(data);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
synchronized (_data) {
|
||||
_data.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,9 +111,10 @@ public class SAMUtils {
|
||||
*/
|
||||
public static Properties parseParams(StringTokenizer tok) throws SAMException {
|
||||
int pos, nprops = 0, ntoks = tok.countTokens();
|
||||
String token, param, value;
|
||||
String token, param;
|
||||
Properties props = new Properties();
|
||||
|
||||
StringBuffer value = new StringBuffer();
|
||||
for (int i = 0; i < ntoks; ++i) {
|
||||
token = tok.nextToken();
|
||||
|
||||
@ -123,9 +124,16 @@ public class SAMUtils {
|
||||
throw new SAMException("Bad formatting for param [" + token + "]");
|
||||
}
|
||||
param = token.substring(0, pos);
|
||||
value = token.substring(pos + 1);
|
||||
value.append(token.substring(pos+1));
|
||||
if (value.charAt(0) == '"') {
|
||||
while ( (i < ntoks) && (value.lastIndexOf("\"") <= 0) ) {
|
||||
value.append(' ').append(tok.nextToken());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
props.setProperty(param, value);
|
||||
props.setProperty(param, value.toString());
|
||||
value.setLength(0);
|
||||
nprops += 1;
|
||||
}
|
||||
|
||||
@ -157,4 +165,19 @@ public class SAMUtils {
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
test("a=b c=d e=\"f g h\"");
|
||||
test("a=\"b c d\" e=\"f g h\" i=\"j\"");
|
||||
test("a=\"b c d\" e=f i=\"j\"");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private static void test(String props) throws Exception {
|
||||
StringTokenizer tok = new StringTokenizer(props);
|
||||
Properties p = parseParams(tok);
|
||||
System.out.println(p);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
@ -25,6 +26,7 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -43,6 +45,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
private SAMDatagramSession datagramSession = null;
|
||||
private SAMStreamSession streamSession = null;
|
||||
|
||||
private long _id;
|
||||
private static volatile long __id = 0;
|
||||
|
||||
/**
|
||||
* Create a new SAM version 1 handler. This constructor expects
|
||||
* that the SAM HELLO message has been still answered (and
|
||||
@ -67,6 +72,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
*/
|
||||
public SAMv1Handler(Socket s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException {
|
||||
super(s, verMajor, verMinor, i2cpProps);
|
||||
_id = ++__id;
|
||||
_log.debug("SAM version 1 handler instantiated");
|
||||
|
||||
if ((this.verMajor != 1) || (this.verMinor != 0)) {
|
||||
@ -75,13 +81,14 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
public void handle() {
|
||||
String msg, domain, opcode;
|
||||
String msg = null;
|
||||
String domain = null;
|
||||
String opcode = null;
|
||||
boolean canContinue = false;
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(IN_BUFSIZE);
|
||||
StringTokenizer tok;
|
||||
Properties props;
|
||||
|
||||
this.thread.setName("SAMv1Handler");
|
||||
this.thread.setName("SAMv1Handler " + _id);
|
||||
_log.debug("SAM handling started");
|
||||
|
||||
try {
|
||||
@ -94,22 +101,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
break;
|
||||
}
|
||||
|
||||
while ((b = in.read()) != -1) {
|
||||
if (b == '\n') {
|
||||
break;
|
||||
}
|
||||
buf.write(b);
|
||||
}
|
||||
if (b == -1) {
|
||||
msg = DataHelper.readLine(in);
|
||||
if (msg == null) {
|
||||
_log.debug("Connection closed by client");
|
||||
break;
|
||||
}
|
||||
|
||||
msg = buf.toString("ISO-8859-1").trim();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("New message received: " + msg);
|
||||
_log.debug("New message received: [" + msg + "]");
|
||||
}
|
||||
buf.reset();
|
||||
|
||||
tok = new StringTokenizer(msg, " ");
|
||||
if (tok.countTokens() < 2) {
|
||||
@ -149,14 +149,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException ("
|
||||
+ e.getMessage() + ")", e);
|
||||
+ e.getMessage() + ") for message [" + msg + "]", e);
|
||||
} catch (Exception e) {
|
||||
_log.error("Unexpected exception", e);
|
||||
_log.error("Unexpected exception for message [" + msg + "]", e);
|
||||
} finally {
|
||||
_log.debug("Stopping handler");
|
||||
try {
|
||||
@ -549,13 +546,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(getClientSocketInputStream());
|
||||
byte[] data = new byte[size];
|
||||
|
||||
in.readFully(data);
|
||||
|
||||
if (!streamSession.sendBytes(id, data)) {
|
||||
_log.error("STREAM SEND failed");
|
||||
if (!streamSession.sendBytes(id, getClientSocketInputStream(), size)) { // data)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("STREAM SEND [" + size + "] failed");
|
||||
boolean rv = writeString("STREAM CLOSED RESULT=CANT_REACH_PEER ID=" + id + " MESSAGE=\"Send of " + size + " bytes failed\"\n");
|
||||
streamSession.closeConnection(id);
|
||||
return rv;
|
||||
@ -563,11 +556,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
_log.debug("Too few bytes with RAW SEND message (expected: "
|
||||
_log.debug("Too few bytes with STREAM SEND message (expected: "
|
||||
+ size);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException while parsing RAW SEND message",
|
||||
_log.debug("Caught IOException while parsing STREAM SEND message",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
@ -685,9 +678,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
|
||||
ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
||||
|
||||
msg.write(("RAW RECEIVED SIZE=" + data.length
|
||||
+ "\n").getBytes("ISO-8859-1"));
|
||||
String msgText = "RAW RECEIVED SIZE=" + data.length + "\n";
|
||||
msg.write(msgText.getBytes("ISO-8859-1"));
|
||||
msg.write(data);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sending to client: " + msgText);
|
||||
|
||||
writeBytes(msg.toByteArray());
|
||||
}
|
||||
@ -703,7 +699,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
try {
|
||||
closeClientSocket();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error closing socket: " + e.getMessage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -716,9 +713,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
|
||||
ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
||||
|
||||
msg.write(("DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
|
||||
+ " SIZE=" + data.length
|
||||
+ "\n").getBytes("ISO-8859-1"));
|
||||
String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
|
||||
+ " SIZE=" + data.length + "\n";
|
||||
msg.write(msgText.getBytes("ISO-8859-1"));
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sending to client: " + msgText);
|
||||
msg.write(data);
|
||||
|
||||
writeBytes(msg.toByteArray());
|
||||
@ -735,7 +735,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
try {
|
||||
closeClientSocket();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error closing socket: " + e.getMessage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,13 +760,24 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
throw new NullPointerException("BUG! STREAM session is null!");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
||||
|
||||
msg.write(("STREAM RECEIVED ID=" + id
|
||||
+" SIZE=" + len + "\n").getBytes("ISO-8859-1"));
|
||||
msg.write(data, 0, len);
|
||||
|
||||
writeBytes(msg.toByteArray());
|
||||
String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + len + "\n";
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sending to client: " + msgText);
|
||||
|
||||
byte prefix[] = msgText.getBytes("ISO-8859-1");
|
||||
|
||||
// dont waste so much memory
|
||||
//ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
||||
//msg.write(msgText.getBytes("ISO-8859-1"));
|
||||
//msg.write(data, 0, len);
|
||||
// writeBytes(msg.toByteArray());
|
||||
Object writeLock = getWriteLock();
|
||||
OutputStream out = getOut();
|
||||
synchronized (writeLock) {
|
||||
out.write(prefix);
|
||||
out.write(data, 0, len);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyStreamDisconnection(int id, String result, String msg) throws IOException {
|
||||
@ -783,7 +795,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
public void stopStreamReceiving() {
|
||||
_log.debug("stopStreamReceiving() invoked");
|
||||
_log.debug("stopStreamReceiving() invoked", new Exception("stopped"));
|
||||
|
||||
if (streamSession == null) {
|
||||
_log.error("BUG! Got stream receiving stop, but session is null!");
|
||||
@ -793,7 +805,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
try {
|
||||
closeClientSocket();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error closing socket: " + e.getMessage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing socket", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Basic noop client event listener
|
||||
*/
|
||||
public class SAMClientEventListenerImpl implements SAMReader.SAMClientEventListener {
|
||||
public void destReplyReceived(String publicKey, String privateKey) {}
|
||||
public void helloReplyReceived(boolean ok) {}
|
||||
public void namingReplyReceived(String name, String result, String value, String message) {}
|
||||
public void sessionStatusReceived(String result, String destination, String message) {}
|
||||
public void streamClosedReceived(String result, int id, String message) {}
|
||||
public void streamConnectedReceived(String remoteDestination, int id) {}
|
||||
public void streamDataReceived(int id, byte[] data, int offset, int length) {}
|
||||
public void streamStatusReceived(String result, int id, String message) {}
|
||||
public void unknownMessageReceived(String major, String minor, Properties params) {}
|
||||
}
|
127
apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
Normal file
127
apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
Normal file
@ -0,0 +1,127 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple helper implementation of a the SAMClientEventListener
|
||||
*
|
||||
*/
|
||||
public class SAMEventHandler extends SAMClientEventListenerImpl {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Boolean _helloOk;
|
||||
private Object _helloLock = new Object();
|
||||
private Boolean _sessionCreateOk;
|
||||
private Object _sessionCreateLock = new Object();
|
||||
private Object _namingReplyLock = new Object();
|
||||
private Map _namingReplies = new HashMap();
|
||||
|
||||
public SAMEventHandler(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
}
|
||||
|
||||
public void helloReplyReceived(boolean ok) {
|
||||
synchronized (_helloLock) {
|
||||
if (ok)
|
||||
_helloOk = Boolean.TRUE;
|
||||
else
|
||||
_helloOk = Boolean.FALSE;
|
||||
_helloLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void sessionStatusReceived(String result, String destination, String msg) {
|
||||
synchronized (_sessionCreateLock) {
|
||||
if (SAMReader.SAMClientEventListener.SESSION_STATUS_OK.equals(result))
|
||||
_sessionCreateOk = Boolean.TRUE;
|
||||
else
|
||||
_sessionCreateOk = Boolean.FALSE;
|
||||
_sessionCreateLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void namingReplyReceived(String name, String result, String value, String msg) {
|
||||
synchronized (_namingReplyLock) {
|
||||
if (SAMReader.SAMClientEventListener.NAMING_REPLY_OK.equals(result))
|
||||
_namingReplies.put(name, value);
|
||||
else
|
||||
_namingReplies.put(name, result);
|
||||
_namingReplyLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void unknownMessageReceived(String major, String minor, Properties params) {
|
||||
_log.error("wrt, [" + major + "] [" + minor + "] [" + params + "]");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// blocking lookup calls below
|
||||
//
|
||||
|
||||
/**
|
||||
* Wait for the connection to be established, returning true if everything
|
||||
* went ok
|
||||
*/
|
||||
public boolean waitForHelloReply() {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_helloLock) {
|
||||
if (_helloOk == null)
|
||||
_helloLock.wait();
|
||||
else
|
||||
return _helloOk.booleanValue();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the session to be created, returning true if everything went ok
|
||||
*
|
||||
*/
|
||||
public boolean waitForSessionCreateReply() {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_sessionCreateLock) {
|
||||
if (_sessionCreateOk == null)
|
||||
_sessionCreateLock.wait();
|
||||
else
|
||||
return _sessionCreateOk.booleanValue();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the destination found matching the name, or null if the key was
|
||||
* not able to be retrieved.
|
||||
*
|
||||
* @param name name to be looked for, or "ME"
|
||||
*/
|
||||
public String waitForNamingReply(String name) {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_namingReplyLock) {
|
||||
String val = (String)_namingReplies.remove(name);
|
||||
if (val == null) {
|
||||
_namingReplyLock.wait();
|
||||
} else {
|
||||
if (SAMReader.SAMClientEventListener.NAMING_REPLY_INVALID_KEY.equals(val))
|
||||
return null;
|
||||
else if (SAMReader.SAMClientEventListener.NAMING_REPLY_KEY_NOT_FOUND.equals(val))
|
||||
return null;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
253
apps/sam/java/src/net/i2p/sam/client/SAMReader.java
Normal file
253
apps/sam/java/src/net/i2p/sam/client/SAMReader.java
Normal file
@ -0,0 +1,253 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Read from a socket, producing events for any SAM message read
|
||||
*
|
||||
*/
|
||||
public class SAMReader {
|
||||
private Log _log;
|
||||
private InputStream _inRaw;
|
||||
private SAMClientEventListener _listener;
|
||||
private boolean _live;
|
||||
|
||||
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
|
||||
_log = context.logManager().getLog(SAMReader.class);
|
||||
_inRaw = samIn;
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
public void startReading() {
|
||||
_live = true;
|
||||
I2PThread t = new I2PThread(new Runner(), "SAM reader");
|
||||
t.start();
|
||||
}
|
||||
public void stopReading() { _live = false; }
|
||||
|
||||
/**
|
||||
* Async event notification interface for SAM clients
|
||||
*
|
||||
*/
|
||||
public interface SAMClientEventListener {
|
||||
public static final String SESSION_STATUS_OK = "OK";
|
||||
public static final String SESSION_STATUS_DUPLICATE_DEST = "DUPLICATE_DEST";
|
||||
public static final String SESSION_STATUS_I2P_ERROR = "I2P_ERROR";
|
||||
public static final String SESSION_STATUS_INVALID_KEY = "INVALID_KEY";
|
||||
|
||||
public static final String STREAM_STATUS_OK = "OK";
|
||||
public static final String STREAM_STATUS_CANT_REACH_PEER = "CANT_REACH_PEER";
|
||||
public static final String STREAM_STATUS_I2P_ERROR = "I2P_ERROR";
|
||||
public static final String STREAM_STATUS_INVALID_KEY = "INVALID_KEY";
|
||||
public static final String STREAM_STATUS_TIMEOUT = "TIMEOUT";
|
||||
|
||||
public static final String STREAM_CLOSED_OK = "OK";
|
||||
public static final String STREAM_CLOSED_CANT_REACH_PEER = "CANT_REACH_PEER";
|
||||
public static final String STREAM_CLOSED_I2P_ERROR = "I2P_ERROR";
|
||||
public static final String STREAM_CLOSED_PEER_NOT_FOUND = "PEER_NOT_FOUND";
|
||||
public static final String STREAM_CLOSED_TIMEOUT = "CLOSED";
|
||||
|
||||
public static final String NAMING_REPLY_OK = "OK";
|
||||
public static final String NAMING_REPLY_INVALID_KEY = "INVALID_KEY";
|
||||
public static final String NAMING_REPLY_KEY_NOT_FOUND = "KEY_NOT_FOUND";
|
||||
|
||||
public void helloReplyReceived(boolean ok);
|
||||
public void sessionStatusReceived(String result, String destination, String message);
|
||||
public void streamStatusReceived(String result, int id, String message);
|
||||
public void streamConnectedReceived(String remoteDestination, int id);
|
||||
public void streamClosedReceived(String result, int id, String message);
|
||||
public void streamDataReceived(int id, byte data[], int offset, int length);
|
||||
public void namingReplyReceived(String name, String result, String value, String message);
|
||||
public void destReplyReceived(String publicKey, String privateKey);
|
||||
|
||||
public void unknownMessageReceived(String major, String minor, Properties params);
|
||||
}
|
||||
|
||||
private class Runner implements Runnable {
|
||||
public void run() {
|
||||
Properties params = new Properties();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(80);
|
||||
while (_live) {
|
||||
|
||||
try {
|
||||
int c = -1;
|
||||
while ((c = _inRaw.read()) != -1) {
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
baos.write(c);
|
||||
}
|
||||
if (c == -1) {
|
||||
_log.error("Error reading from the SAM bridge");
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading from SAM", ioe);
|
||||
}
|
||||
|
||||
String line = new String(baos.toByteArray());
|
||||
baos.reset();
|
||||
|
||||
if (line == null) {
|
||||
_log.info("No more data from the SAM bridge");
|
||||
break;
|
||||
}
|
||||
|
||||
_log.debug("Line read from the bridge: " + line);
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
|
||||
if (tok.countTokens() < 2) {
|
||||
_log.error("Invalid SAM line: [" + line + "]");
|
||||
_live = false;
|
||||
return;
|
||||
}
|
||||
|
||||
String major = tok.nextToken();
|
||||
String minor = tok.nextToken();
|
||||
|
||||
params.clear();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq > 0) && (eq < pair.length() - 1) ) {
|
||||
String name = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
while ( (val.charAt(0) == '\"') && (val.length() > 0) )
|
||||
val = val.substring(1);
|
||||
while ( (val.length() > 0) && (val.charAt(val.length()-1) == '\"') )
|
||||
val = val.substring(0, val.length()-1);
|
||||
params.setProperty(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
processEvent(major, minor, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Big ugly method parsing everything. If I cared, I'd factor this out into
|
||||
* a dozen tiny methods.
|
||||
*
|
||||
*/
|
||||
private void processEvent(String major, String minor, Properties params) {
|
||||
if ("HELLO".equals(major)) {
|
||||
if ("REPLY".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
if ("OK".equals(result))
|
||||
_listener.helloReplyReceived(true);
|
||||
else
|
||||
_listener.helloReplyReceived(false);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("SESSION".equals(major)) {
|
||||
if ("STATUS".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
String dest = params.getProperty("DESTINATION");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
_listener.sessionStatusReceived(result, dest, msg);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("STREAM".equals(major)) {
|
||||
if ("STATUS".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
String id = params.getProperty("ID");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
if (id != null) {
|
||||
try {
|
||||
_listener.streamStatusReceived(result, Integer.parseInt(id), msg);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("CONNECTED".equals(minor)) {
|
||||
String dest = params.getProperty("DESTINATION");
|
||||
String id = params.getProperty("ID");
|
||||
if (id != null) {
|
||||
try {
|
||||
_listener.streamConnectedReceived(dest, Integer.parseInt(id));
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("CLOSED".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
String id = params.getProperty("ID");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
if (id != null) {
|
||||
try {
|
||||
_listener.streamClosedReceived(result, Integer.parseInt(id), msg);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("RECEIVED".equals(minor)) {
|
||||
String id = params.getProperty("ID");
|
||||
String size = params.getProperty("SIZE");
|
||||
if (id != null) {
|
||||
try {
|
||||
int idVal = Integer.parseInt(id);
|
||||
int sizeVal = Integer.parseInt(size);
|
||||
|
||||
byte data[] = new byte[sizeVal];
|
||||
int read = DataHelper.read(_inRaw, data);
|
||||
if (read != sizeVal) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
} else {
|
||||
_listener.streamDataReceived(idVal, data, 0, sizeVal);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
} catch (IOException ioe) {
|
||||
_live = false;
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("NAMING".equals(major)) {
|
||||
if ("REPLY".equals(minor)) {
|
||||
String name = params.getProperty("NAME");
|
||||
String result = params.getProperty("RESULT");
|
||||
String value = params.getProperty("VALUE");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
_listener.namingReplyReceived(name, result, value, msg);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("DEST".equals(major)) {
|
||||
if ("REPLY".equals(minor)) {
|
||||
String pub = params.getProperty("PUB");
|
||||
String priv = params.getProperty("PRIV");
|
||||
_listener.destReplyReceived(pub, priv);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
}
|
||||
}
|
262
apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
Normal file
262
apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
Normal file
@ -0,0 +1,262 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.sam.client.SAMEventHandler;
|
||||
import net.i2p.sam.client.SAMClientEventListenerImpl;
|
||||
import net.i2p.sam.client.SAMReader;
|
||||
|
||||
/**
|
||||
* Send a file to a peer
|
||||
*
|
||||
* Usage: SAMStreamSend samHost samPort peerDestFile dataFile
|
||||
*
|
||||
*/
|
||||
public class SAMStreamSend {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _samHost;
|
||||
private String _samPort;
|
||||
private String _destFile;
|
||||
private String _dataFile;
|
||||
private String _conOptions;
|
||||
private Socket _samSocket;
|
||||
private OutputStream _samOut;
|
||||
private InputStream _samIn;
|
||||
private SAMReader _reader;
|
||||
private boolean _dead;
|
||||
private SAMEventHandler _eventHandler;
|
||||
/** Connection id (Integer) to peer (Flooder) */
|
||||
private Map _remotePeers;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 4) {
|
||||
System.err.println("Usage: SAMStreamSend samHost samPort peerDestFile dataFile");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 3];
|
||||
SAMStreamSend sender = new SAMStreamSend(ctx, args[0], args[1], args[2], args[3]);
|
||||
sender.startup();
|
||||
}
|
||||
|
||||
public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(SAMStreamSend.class);
|
||||
_dead = false;
|
||||
_samHost = samHost;
|
||||
_samPort = samPort;
|
||||
_destFile = destFile;
|
||||
_dataFile = dataFile;;
|
||||
_conOptions = "";
|
||||
_eventHandler = new SendEventHandler(_context);
|
||||
_remotePeers = new HashMap();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
boolean ok = connect();
|
||||
_log.debug("Connected: " + ok);
|
||||
if (ok) {
|
||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
||||
_reader.startReading();
|
||||
_log.debug("Reader created");
|
||||
String ourDest = handshake();
|
||||
_log.debug("Handshake complete. we are " + ourDest);
|
||||
if (ourDest != null) {
|
||||
send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SendEventHandler extends SAMEventHandler {
|
||||
public SendEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||
public void streamClosedReceived(String result, int id, String message) {
|
||||
Sender sender = null;
|
||||
synchronized (_remotePeers) {
|
||||
sender = (Sender)_remotePeers.remove(new Integer(id));
|
||||
}
|
||||
if (sender != null) {
|
||||
sender.closed();
|
||||
_log.debug("Connection " + sender.getConnectionId() + " closed to " + sender.getDestination());
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
try {
|
||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
||||
_samOut = _samSocket.getOutputStream();
|
||||
_samIn = _samSocket.getInputStream();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
_log.debug("Hello reply found: " + ok);
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=TRANSIENT " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
_log.debug("Naming lookup reply found: " + destination);
|
||||
if (destination == null) {
|
||||
_log.error("No naming lookup reply found!");
|
||||
return null;
|
||||
} else {
|
||||
_log.info("We are " + destination);
|
||||
}
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handshaking", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send() {
|
||||
Sender sender = new Sender();
|
||||
boolean ok = sender.openConnection();
|
||||
if (ok) {
|
||||
I2PThread t = new I2PThread(sender, "Sender");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private class Sender implements Runnable {
|
||||
private int _connectionId;
|
||||
private String _remoteDestination;
|
||||
private InputStream _in;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
|
||||
public Sender() {
|
||||
_closed = false;
|
||||
}
|
||||
|
||||
public boolean openConnection() {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_destFile);
|
||||
byte dest[] = new byte[1024];
|
||||
int read = DataHelper.read(fin, dest);
|
||||
|
||||
_remoteDestination = new String(dest, 0, read);
|
||||
synchronized (_remotePeers) {
|
||||
_connectionId = _remotePeers.size() + 1;
|
||||
_remotePeers.put(new Integer(_connectionId), Sender.this);
|
||||
}
|
||||
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
|
||||
byte msg[] = ("STREAM CONNECT ID=" + _connectionId + " DESTINATION=" + _remoteDestination + "\n").getBytes();
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
}
|
||||
|
||||
_in = new FileInputStream(_dataFile);
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to connect", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getConnectionId() { return _connectionId; }
|
||||
public String getDestination() { return _remoteDestination; }
|
||||
|
||||
public void closed() {
|
||||
if (_closed) return;
|
||||
_closed = true;
|
||||
long lifetime = _context.clock().now() - _started;
|
||||
_context.statManager().addRateData("send." + _connectionId + ".lifetime", lifetime, lifetime);
|
||||
try { _in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("send." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[1024];
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
while (!_closed) {
|
||||
try {
|
||||
int read = _in.read(data);
|
||||
long now = _context.clock().now();
|
||||
if (read == -1) {
|
||||
_log.debug("EOF from the data for " + _connectionId + " after " + (now-lastSend));
|
||||
break;
|
||||
} else if (read > 0) {
|
||||
_log.debug("Sending " + read + " on " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
|
||||
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + read + "\n").getBytes();
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.write(data, 0, read);
|
||||
_samOut.flush();
|
||||
}
|
||||
|
||||
_totalSent += read;
|
||||
_context.statManager().addRateData("send." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error sending", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes();
|
||||
try {
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing", ioe);
|
||||
}
|
||||
|
||||
closed();
|
||||
}
|
||||
}
|
||||
}
|
247
apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
Normal file
247
apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
Normal file
@ -0,0 +1,247 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.sam.client.SAMEventHandler;
|
||||
import net.i2p.sam.client.SAMClientEventListenerImpl;
|
||||
import net.i2p.sam.client.SAMReader;
|
||||
|
||||
/**
|
||||
* Sit around on a SAM destination, receiving lots of data and
|
||||
* writing it to disk
|
||||
*
|
||||
* Usage: SAMStreamSink samHost samPort myKeyFile sinkDir
|
||||
*
|
||||
*/
|
||||
public class SAMStreamSink {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _samHost;
|
||||
private String _samPort;
|
||||
private String _destFile;
|
||||
private String _sinkDir;
|
||||
private String _conOptions;
|
||||
private Socket _samSocket;
|
||||
private OutputStream _samOut;
|
||||
private InputStream _samIn;
|
||||
private SAMReader _reader;
|
||||
private boolean _dead;
|
||||
private SAMEventHandler _eventHandler;
|
||||
/** Connection id (Integer) to peer (Flooder) */
|
||||
private Map _remotePeers;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 4) {
|
||||
System.err.println("Usage: SAMStreamSink samHost samPort myDestFile sinkDir");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
SAMStreamSink sink = new SAMStreamSink(ctx, args[0], args[1], args[2], args[3]);
|
||||
sink.startup();
|
||||
}
|
||||
|
||||
public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(SAMStreamSink.class);
|
||||
_dead = false;
|
||||
_samHost = samHost;
|
||||
_samPort = samPort;
|
||||
_destFile = destFile;
|
||||
_sinkDir = sinkDir;
|
||||
_conOptions = "";
|
||||
_eventHandler = new SinkEventHandler(_context);
|
||||
_remotePeers = new HashMap();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
boolean ok = connect();
|
||||
_log.debug("Connected: " + ok);
|
||||
if (ok) {
|
||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
||||
_reader.startReading();
|
||||
_log.debug("Reader created");
|
||||
String ourDest = handshake();
|
||||
_log.debug("Handshake complete. we are " + ourDest);
|
||||
if (ourDest != null) {
|
||||
boolean written = writeDest(ourDest);
|
||||
_log.debug("Dest written");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SinkEventHandler extends SAMEventHandler {
|
||||
public SinkEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||
public void streamClosedReceived(String result, int id, String message) {
|
||||
Sink sink = null;
|
||||
synchronized (_remotePeers) {
|
||||
sink = (Sink)_remotePeers.remove(new Integer(id));
|
||||
}
|
||||
if (sink != null) {
|
||||
sink.closed();
|
||||
_log.debug("Connection " + sink.getConnectionId() + " closed to " + sink.getDestination());
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
||||
}
|
||||
}
|
||||
public void streamDataReceived(int id, byte data[], int offset, int length) {
|
||||
Sink sink = null;
|
||||
synchronized (_remotePeers) {
|
||||
sink = (Sink)_remotePeers.get(new Integer(id));
|
||||
}
|
||||
if (sink != null) {
|
||||
sink.received(data, offset, length);
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we received " + length + "?");
|
||||
}
|
||||
}
|
||||
public void streamConnectedReceived(String dest, int id) {
|
||||
_log.debug("Connection " + id + " received from " + dest);
|
||||
|
||||
try {
|
||||
Sink sink = new Sink(id, dest);
|
||||
synchronized (_remotePeers) {
|
||||
_remotePeers.put(new Integer(id), sink);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating a new sink", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
try {
|
||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
||||
_samOut = _samSocket.getOutputStream();
|
||||
_samIn = _samSocket.getInputStream();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
_log.debug("Hello reply found: " + ok);
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
_log.debug("Naming lookup reply found: " + destination);
|
||||
if (destination == null) {
|
||||
_log.error("No naming lookup reply found!");
|
||||
return null;
|
||||
} else {
|
||||
_log.info(_destFile + " is located at " + destination);
|
||||
}
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handshaking", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeDest(String dest) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(_destFile);
|
||||
fos.write(dest.getBytes());
|
||||
fos.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error writing to " + _destFile, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class Sink {
|
||||
private int _connectionId;
|
||||
private String _remoteDestination;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalReceived;
|
||||
private long _lastReceivedOn;
|
||||
private OutputStream _out;
|
||||
|
||||
public Sink(int conId, String remDest) throws IOException {
|
||||
_connectionId = conId;
|
||||
_remoteDestination = remDest;
|
||||
_closed = false;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("sink." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("sink." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("sink." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
|
||||
File sinkDir = new File(_sinkDir);
|
||||
if (!sinkDir.exists())
|
||||
sinkDir.mkdirs();
|
||||
|
||||
File out = File.createTempFile("sink", ".dat", sinkDir);
|
||||
_out = new FileOutputStream(out);
|
||||
}
|
||||
|
||||
public int getConnectionId() { return _connectionId; }
|
||||
public String getDestination() { return _remoteDestination; }
|
||||
|
||||
public void closed() {
|
||||
if (_closed) return;
|
||||
_closed = true;
|
||||
long lifetime = _context.clock().now() - _started;
|
||||
_context.statManager().addRateData("sink." + _connectionId + ".lifetime", lifetime, lifetime);
|
||||
try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing", ioe);
|
||||
}
|
||||
}
|
||||
public void received(byte data[], int offset, int len) {
|
||||
if (_closed) return;
|
||||
_totalReceived += len;
|
||||
try {
|
||||
_out.write(data, offset, len);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing received data");
|
||||
closed();
|
||||
return;
|
||||
}
|
||||
_log.debug("Received " + len + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
|
||||
+ "ms with " + _remoteDestination.substring(0,6));
|
||||
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,11 @@ public class TestStreamTransfer {
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
startAlice(samHost, samPort, conOptions);
|
||||
testBob(samHost, samPort, conOptions);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
testBob("bob" + i, samHost, samPort, conOptions);
|
||||
if (i % 2 == 1)
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void startAlice(String host, int port, String conOptions) {
|
||||
@ -95,11 +99,13 @@ public class TestStreamTransfer {
|
||||
try { _out.close(); } catch (IOException ioe) {}
|
||||
try { _s.close(); } catch (IOException ioe) {}
|
||||
_streams.clear();
|
||||
_dead = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void doRun() throws IOException, SAMException {
|
||||
String line = _reader.readLine();
|
||||
_log.debug("Read: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
@ -144,14 +150,17 @@ public class TestStreamTransfer {
|
||||
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
|
||||
return;
|
||||
}
|
||||
_log.info("Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
_log.info("\n== Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
/*
|
||||
// now echo it back
|
||||
String reply = "STREAM SEND ID=" + id +
|
||||
" SIZE=" + payloadSize +
|
||||
"\n" + payload;
|
||||
"\n" + new String(payload);
|
||||
_out.write(reply.getBytes());
|
||||
_out.flush();
|
||||
_log.info("Reply sent back [" + new String(reply.getBytes()) + "]");
|
||||
*/
|
||||
} else {
|
||||
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
|
||||
return;
|
||||
@ -159,8 +168,27 @@ public class TestStreamTransfer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void testBob(String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting Bob\n\n\n");
|
||||
private static void testBob(String sessionName, String host, int port, String conOptions) {
|
||||
I2PThread t = new I2PThread(new TestBob(sessionName, host, port, conOptions), sessionName);
|
||||
t.start();
|
||||
}
|
||||
private static class TestBob implements Runnable {
|
||||
private String _sessionName;
|
||||
private String _host;
|
||||
private int _port;
|
||||
private String _opts;
|
||||
public TestBob(String name, String host, int port, String opts) {
|
||||
_sessionName = name;
|
||||
_host = host;
|
||||
_port = port;
|
||||
_opts = opts;
|
||||
}
|
||||
public void run() {
|
||||
doTestBob(_sessionName, _host, _port, _opts);
|
||||
}
|
||||
}
|
||||
private static void doTestBob(String sessionName, String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting " + sessionName + "\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
@ -168,32 +196,42 @@ public class TestStreamTransfer {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Bob " + conOptions + "\n";
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + sessionName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Bob: " + line);
|
||||
_log.info("Response to creating the session with destination "+ sessionName+": " + line);
|
||||
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to the stream connect from Bob to Alice: " + line);
|
||||
_log.info("Response to the stream connect from "+sessionName+" to Alice: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
_log.info("props = " + props);
|
||||
String result = props.getProperty("RESULT");
|
||||
if (!("OK".equals(result))) {
|
||||
_log.error("Unable to connect!");
|
||||
_dead = true;
|
||||
//_dead = true;
|
||||
return;
|
||||
}
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
|
||||
_log.info("\n** Sending BlahBlah!!");
|
||||
out.write(req.getBytes());
|
||||
out.flush();
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nFooBarBaz!";
|
||||
_log.info("\n** Sending FooBarBaz!");
|
||||
out.write(req.getBytes());
|
||||
out.flush();
|
||||
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
|
||||
_log.info("Sending close");
|
||||
req = "STREAM CLOSE ID=42\n";
|
||||
out.write(req.getBytes());
|
||||
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
|
||||
_dead = true;
|
||||
out.flush();
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
//_dead = true;
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
@ -203,7 +241,7 @@ public class TestStreamTransfer {
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=10001 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
@ -215,8 +253,8 @@ public class TestStreamTransfer {
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
//try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
//System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
312
apps/sam/java/test/net/i2p/sam/TestSwarm.java
Normal file
312
apps/sam/java/test/net/i2p/sam/TestSwarm.java
Normal file
@ -0,0 +1,312 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.sam.client.SAMEventHandler;
|
||||
import net.i2p.sam.client.SAMClientEventListenerImpl;
|
||||
import net.i2p.sam.client.SAMReader;
|
||||
|
||||
/**
|
||||
* Sit around on a SAM destination, receiving lots of data and sending lots of
|
||||
* data to whomever talks to us.
|
||||
*
|
||||
* Usage: TestSwarm samHost samPort myKeyFile [peerDestFile ]*
|
||||
*
|
||||
*/
|
||||
public class TestSwarm {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _samHost;
|
||||
private String _samPort;
|
||||
private String _destFile;
|
||||
private String _peerDestFiles[];
|
||||
private String _conOptions;
|
||||
private Socket _samSocket;
|
||||
private OutputStream _samOut;
|
||||
private InputStream _samIn;
|
||||
private SAMReader _reader;
|
||||
private boolean _dead;
|
||||
private SAMEventHandler _eventHandler;
|
||||
/** Connection id (Integer) to peer (Flooder) */
|
||||
private Map _remotePeers;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 3) {
|
||||
System.err.println("Usage: TestSwarm samHost samPort myDestFile [peerDestFile ]*");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 3];
|
||||
System.arraycopy(args, 3, files, 0, files.length);
|
||||
TestSwarm swarm = new TestSwarm(ctx, args[0], args[1], args[2], files);
|
||||
swarm.startup();
|
||||
}
|
||||
|
||||
public TestSwarm(I2PAppContext ctx, String samHost, String samPort, String destFile, String peerDestFiles[]) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(TestSwarm.class);
|
||||
_dead = false;
|
||||
_samHost = samHost;
|
||||
_samPort = samPort;
|
||||
_destFile = destFile;
|
||||
_peerDestFiles = peerDestFiles;
|
||||
_conOptions = "";
|
||||
_eventHandler = new SwarmEventHandler(_context);
|
||||
_remotePeers = new HashMap();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
boolean ok = connect();
|
||||
_log.debug("Connected: " + ok);
|
||||
if (ok) {
|
||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
||||
_reader.startReading();
|
||||
_log.debug("Reader created");
|
||||
String ourDest = handshake();
|
||||
_log.debug("Handshake complete. we are " + ourDest);
|
||||
if (ourDest != null) {
|
||||
boolean written = writeDest(ourDest);
|
||||
_log.debug("Dest written");
|
||||
if (written) {
|
||||
connectWithPeers();
|
||||
_log.debug("connected with peers");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SwarmEventHandler extends SAMEventHandler {
|
||||
public SwarmEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||
public void streamClosedReceived(String result, int id, String message) {
|
||||
Flooder flooder = null;
|
||||
synchronized (_remotePeers) {
|
||||
flooder = (Flooder)_remotePeers.remove(new Integer(id));
|
||||
}
|
||||
if (flooder != null) {
|
||||
flooder.closed();
|
||||
_log.debug("Connection " + flooder.getConnectionId() + " closed to " + flooder.getDestination());
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
||||
}
|
||||
}
|
||||
public void streamDataReceived(int id, byte data[], int offset, int length) {
|
||||
Flooder flooder = null;
|
||||
synchronized (_remotePeers) {
|
||||
flooder = (Flooder)_remotePeers.get(new Integer(id));
|
||||
}
|
||||
long value = DataHelper.fromLong(data, 0, 4);
|
||||
if (flooder != null) {
|
||||
flooder.received(length, value);
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we received " + value + "?");
|
||||
}
|
||||
}
|
||||
public void streamConnectedReceived(String dest, int id) {
|
||||
_log.debug("Connection " + id + " received from " + dest);
|
||||
|
||||
Flooder flooder = new Flooder(id, dest);
|
||||
synchronized (_remotePeers) {
|
||||
_remotePeers.put(new Integer(id), flooder);
|
||||
}
|
||||
I2PThread t = new I2PThread(flooder, "Flood " + id);
|
||||
t.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
try {
|
||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
||||
_samOut = _samSocket.getOutputStream();
|
||||
_samIn = _samSocket.getInputStream();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
_log.debug("Hello reply found: " + ok);
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
_log.debug("Naming lookup reply found: " + destination);
|
||||
if (destination == null) {
|
||||
_log.error("No naming lookup reply found!");
|
||||
return null;
|
||||
} else {
|
||||
_log.info(_destFile + " is located at " + destination);
|
||||
}
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handshaking", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeDest(String dest) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(_destFile);
|
||||
fos.write(dest.getBytes());
|
||||
fos.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error writing to " + _destFile, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void connectWithPeers() {
|
||||
if (_peerDestFiles != null) {
|
||||
for (int i = 0; i < _peerDestFiles.length; i++) {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
|
||||
byte dest[] = new byte[1024];
|
||||
int read = DataHelper.read(fin, dest);
|
||||
|
||||
String remDest = new String(dest, 0, read);
|
||||
int con = 0;
|
||||
Flooder flooder = null;
|
||||
synchronized (_remotePeers) {
|
||||
con = _remotePeers.size() + 1;
|
||||
flooder = new Flooder(con, remDest);
|
||||
_remotePeers.put(new Integer(con), flooder);
|
||||
}
|
||||
|
||||
byte msg[] = ("STREAM CONNECT ID=" + con + " DESTINATION=" + remDest + "\n").getBytes();
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
}
|
||||
I2PThread flood = new I2PThread(flooder, "Flood " + con);
|
||||
flood.start();
|
||||
_log.debug("Starting flooder with peer from " + _peerDestFiles[i] + ": " + con);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the peer from " + _peerDestFiles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Flooder implements Runnable {
|
||||
private int _connectionId;
|
||||
private String _remoteDestination;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
private long _totalReceived;
|
||||
private long _lastReceived;
|
||||
private long _lastReceivedOn;
|
||||
private boolean _outOfSync;
|
||||
|
||||
public Flooder(int conId, String remDest) {
|
||||
_connectionId = conId;
|
||||
_remoteDestination = remDest;
|
||||
_closed = false;
|
||||
_outOfSync = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + conId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public int getConnectionId() { return _connectionId; }
|
||||
public String getDestination() { return _remoteDestination; }
|
||||
|
||||
public void closed() {
|
||||
_closed = true;
|
||||
long lifetime = _context.clock().now() - _started;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".lifetime", lifetime, lifetime);
|
||||
}
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[32*1024];
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
while (!_closed) {
|
||||
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + data.length + "\n").getBytes();
|
||||
DataHelper.toLong(data, 0, 4, value);
|
||||
try {
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.write(data);
|
||||
_samOut.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error talking to SAM", ioe);
|
||||
return;
|
||||
}
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
value++;
|
||||
try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
_log.debug("Sending " + value + " on " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
}
|
||||
}
|
||||
public void received(int len, long value) {
|
||||
_totalReceived += len;
|
||||
if ( (!_outOfSync) && (len % 32*1024 != 0) ) {
|
||||
_outOfSync = true;
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Out of sync (len=" + len + " after " + (_totalReceived-len) + ")");
|
||||
}
|
||||
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
|
||||
if (value != _lastReceived + 1) {
|
||||
if (!_outOfSync)
|
||||
_log.error("Received " + value + " when expecting " + (_lastReceived+1) + " on "
|
||||
+ _connectionId + " with " + _remoteDestination.substring(0,6));
|
||||
else
|
||||
_log.debug("(out of sync) Received " + value + " when expecting " + (_lastReceived+1) + " on "
|
||||
+ _connectionId + " with " + _remoteDestination.substring(0,6));
|
||||
} else {
|
||||
_log.debug("Received " + value + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
|
||||
+ "ms with " + _remoteDestination.substring(0,6));
|
||||
}
|
||||
_lastReceived = value;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
}
|
||||
}
|
||||
}
|
@ -33,9 +33,8 @@ def urlopen(url, eepaddr=eepaddr):
|
||||
eepaddr = eepaddr.rstrip('/')
|
||||
|
||||
proxy = urllib2.ProxyHandler( \
|
||||
{'http': 'http://anonymous:passwd@' + eepaddr})
|
||||
opener = urllib2.build_opener(proxy, \
|
||||
urllib2.HTTPBasicAuthHandler(), urllib2.HTTPHandler)
|
||||
{'http': 'http://' + eepaddr})
|
||||
opener = urllib2.build_opener(proxy, urllib2.HTTPHandler)
|
||||
return opener.open(url)
|
||||
|
||||
def urlget(url, eepaddr=eepaddr):
|
||||
|
@ -40,7 +40,7 @@ class Poll:
|
||||
del self.fds[self._hash(fd)]
|
||||
def poll(self, timeout=None):
|
||||
readlist, writelist, errlist = [], [], []
|
||||
for F, mask in self.fds:
|
||||
for F, mask in self.fds.values():
|
||||
if mask & POLLIN: readlist += [F]
|
||||
if mask & POLLOUT: writelist += [F]
|
||||
if mask & POLLERR: errlist += [F]
|
||||
|
42
apps/streaming/java/build.xml
Normal file
42
apps/streaming/java/build.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="streaming">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src:./test"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/streaming.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</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="Streaming" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user