forked from I2P_Developers/i2p.i2p
Compare commits
77 Commits
i2p_0_4_2_
...
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 |
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>
|
@ -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,6 +283,7 @@ 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");
|
||||
@ -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 />
|
||||
|
@ -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,12 +36,12 @@ 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;
|
||||
protected I2PSocketManager sockMgr;
|
||||
protected List mySockets = new ArrayList();
|
||||
|
||||
protected Destination dest = null;
|
||||
@ -58,7 +61,30 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private String handlerName;
|
||||
|
||||
private Object conLock = new Object();
|
||||
private int pendingConnections = 0;
|
||||
|
||||
/** 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) {
|
||||
@ -108,6 +134,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
configurePool(tunnel);
|
||||
|
||||
if (open && listenerReady) {
|
||||
l.log("Ready! Port " + getLocalPort());
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
@ -116,6 +144,37 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
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;
|
||||
|
||||
@ -250,6 +309,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
return;
|
||||
}
|
||||
ss = new ServerSocket(localPort, 0, addr);
|
||||
@ -292,6 +352,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
synchronized (_waitingSockets) {
|
||||
_waitingSockets.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,16 +363,52 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* @param s Socket to take care of
|
||||
*/
|
||||
protected void manageConnection(Socket s) {
|
||||
boolean useBlocking = false;
|
||||
synchronized (conLock) {
|
||||
pendingConnections++;
|
||||
if (pendingConnections > 5)
|
||||
useBlocking = true;
|
||||
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");
|
||||
}
|
||||
}
|
||||
if (useBlocking)
|
||||
clientConnectionRun(s);
|
||||
else
|
||||
new ClientConnectionRunner(s, handlerName);
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
@ -341,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) {
|
||||
@ -352,22 +453,29 @@ 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);
|
||||
synchronized (conLock) {
|
||||
pendingConnections--;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,10 +152,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
I2PSocketOptions opts = super.getDefaultOptions();
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
opts.setReadTimeout(DEFAULT_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;
|
||||
}
|
||||
|
||||
@ -164,14 +168,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
|
||||
I2PSocketOptions opts = super.getDefaultOptions(overrides);
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_READ_TIMEOUT);
|
||||
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;
|
||||
@ -348,7 +356,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)");
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
@ -398,7 +406,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
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, mySockets);
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
@ -428,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 + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,21 +40,37 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
Object slock, finishLock = new Object();
|
||||
boolean finished = false;
|
||||
HashMap ostreams, sockets;
|
||||
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;
|
||||
|
||||
private volatile long __forwarderId;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList) {
|
||||
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))
|
||||
@ -97,24 +113,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
|
||||
public void run() {
|
||||
boolean closedCleanly = false;
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = s.getOutputStream(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
i2ps.setSocketErrorListener(this);
|
||||
InputStream i2pin = i2ps.getInputStream();
|
||||
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialData != null) {
|
||||
if (initialI2PData != null) {
|
||||
synchronized (slock) {
|
||||
i2pout.write(initialData);
|
||||
i2pout.write(initialI2PData);
|
||||
//i2pout.flush();
|
||||
}
|
||||
}
|
||||
if (initialSocketData != null) {
|
||||
out.write(initialSocketData);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Initial data " + (initialData != null ? initialData.length : 0)
|
||||
+ " written, starting forwarders");
|
||||
Thread t1 = new StreamForwarder(in, i2pout, "toI2P");
|
||||
Thread t2 = new StreamForwarder(i2pin, out, "fromI2P");
|
||||
_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();
|
||||
@ -122,12 +141,21 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
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();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
closedCleanly = true;
|
||||
} catch (InterruptedException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
@ -140,21 +168,20 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
} finally {
|
||||
removeRef();
|
||||
try {
|
||||
if ( (s != null) && (!closedCleanly) )
|
||||
if (s != null)
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close java socket", ex);
|
||||
}
|
||||
try {
|
||||
if (i2ps != null) {
|
||||
if (!closedCleanly)
|
||||
i2ps.close();
|
||||
i2ps.setSocketErrorListener(null);
|
||||
if (i2ps != null) {
|
||||
try {
|
||||
i2ps.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close I2PSocket", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close I2PSocket", ex);
|
||||
i2ps.setSocketErrorListener(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,12 +208,14 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
String direction;
|
||||
private boolean _toI2P;
|
||||
private ByteCache _cache;
|
||||
|
||||
private StreamForwarder(InputStream in, OutputStream out, String dir) {
|
||||
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
direction = dir;
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(256, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
@ -207,6 +236,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
|
||||
if (len > 0) updateActivity();
|
||||
|
||||
@ -242,8 +275,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
+ ex.getMessage() + "\")");
|
||||
} catch (IOException ex) {
|
||||
if (!finished) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(direction + ": Error forwarding", ex);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error forwarding", ex);
|
||||
}
|
||||
//else
|
||||
// _log.warn("You may ignore this", ex);
|
||||
@ -259,10 +292,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
_log.warn(direction + ": Error closing input stream", ex);
|
||||
}
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error closing output stream", ex);
|
||||
_log.warn(direction + ": Error flushing to close", ioe);
|
||||
}
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
@ -57,7 +58,7 @@ 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();
|
||||
_starting = getStartOnLoad();
|
||||
}
|
||||
@ -99,6 +100,11 @@ 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)
|
||||
*
|
||||
@ -132,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 + "]");
|
||||
@ -206,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) ) {
|
||||
@ -233,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;
|
||||
@ -294,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"); }
|
||||
@ -311,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);
|
||||
}
|
||||
@ -364,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) )
|
||||
@ -375,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");
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
@ -103,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\" ");
|
||||
@ -129,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)
|
||||
*
|
||||
@ -285,7 +330,7 @@ class WebEditPageFormGenerator {
|
||||
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=\"");
|
||||
|
@ -38,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;
|
||||
@ -139,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);
|
||||
@ -252,6 +257,8 @@ public class WebEditPageHelper {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", _privKeyGenerate);
|
||||
TunnelControllerGroup.getInstance().addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
@ -320,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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -105,4 +105,11 @@ public interface I2PSocketManager {
|
||||
public void setName(String name);
|
||||
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
|
||||
|
||||
public void addDisconnectListener(DisconnectListener lsnr);
|
||||
public void removeDisconnectListener(DisconnectListener lsnr);
|
||||
|
||||
public static interface DisconnectListener {
|
||||
public void sessionDisconnected();
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +100,10 @@ public class I2PSocketManagerFactory {
|
||||
//p.setProperty("tunnels.depthInbound", "0");
|
||||
}
|
||||
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
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);
|
||||
|
@ -10,9 +10,11 @@ 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;
|
||||
|
||||
@ -46,6 +48,7 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private List _listeners;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
@ -76,6 +79,7 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
_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 });
|
||||
@ -109,6 +113,15 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
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) {
|
||||
@ -707,6 +720,17 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
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";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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); }
|
||||
}
|
@ -66,9 +66,9 @@
|
||||
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
|
||||
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
|
||||
<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
|
||||
|
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" />
|
@ -33,7 +33,7 @@ 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,
|
||||
|
@ -23,7 +23,7 @@
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
|
||||
}
|
||||
if (helper.getActiveProfiles() <= 4) { // 4 is the min fallback
|
||||
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 {
|
||||
|
@ -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,10 +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
|
||||
* Switch to GNU Autoconf (?)
|
||||
* Improve build system
|
||||
|
||||
Anyone can help with these things:
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
/* 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
|
||||
|
@ -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));
|
||||
|
@ -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,15 +28,20 @@
|
||||
* 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 <stddef.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
|
||||
|
||||
|
||||
/*
|
||||
@ -67,18 +72,6 @@ extern "C" {
|
||||
* Some LibSAM 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
|
||||
|
||||
typedef enum {SAM_STREAM, SAM_DGRAM, SAM_RAW} sam_conn_t; /* SAM connection */
|
||||
|
||||
typedef char sam_pubkey_t[SAM_PUBKEY_LEN]; /* base 64 public key */
|
||||
@ -88,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 */
|
||||
@ -119,8 +113,8 @@ void sam_session_free(sam_sess_t **session);
|
||||
/* SAM controls - connection */
|
||||
bool sam_close(sam_sess_t *session);
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost,
|
||||
uint16_t samport, const char *destname, sam_conn_t style,
|
||||
uint_t tunneldepth);
|
||||
unsigned short samport, const char *destname, sam_conn_t style,
|
||||
unsigned int tunneldepth);
|
||||
/* SAM controls - utilities */
|
||||
void sam_naming_lookup(sam_sess_t *session, const char *name);
|
||||
bool sam_read_buffer(sam_sess_t *session);
|
||||
@ -128,7 +122,8 @@ const char *sam_strerror(samerr_t code);
|
||||
/* SAM controls - callbacks */
|
||||
void (*sam_diedback)(sam_sess_t *session);
|
||||
void (*sam_logback)(char *str);
|
||||
void (*sam_namingback)(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
void (*sam_namingback)(sam_sess_t *session, char *name,
|
||||
sam_pubkey_t pubkey, samerr_t result);
|
||||
|
||||
/* Stream commands */
|
||||
void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id);
|
||||
@ -168,4 +163,4 @@ 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();
|
||||
@ -79,7 +79,8 @@ void (*sam_diedback)(sam_sess_t *session) = NULL;
|
||||
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,
|
||||
@ -146,8 +147,8 @@ bool sam_close(sam_sess_t *session)
|
||||
*
|
||||
* 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;
|
||||
@ -386,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) {
|
||||
@ -394,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) {
|
||||
@ -402,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;
|
||||
@ -723,10 +724,10 @@ static ssize_t sam_read2(sam_sess_t *session, void *buf, size_t n)
|
||||
p = buf;
|
||||
printf("*RR* ");
|
||||
for (size_t x = 0; x < n; x++) {
|
||||
if (isprint(((uchar_t*)p)[x]))
|
||||
printf("%c,", ((uchar_t*)p)[x]);
|
||||
if (isprint(((byte*)p)[x]))
|
||||
printf("%c,", ((byte*)p)[x]);
|
||||
else
|
||||
printf("%03d,", ((uint8_t*)p)[x]);
|
||||
printf("%03d,", ((byte*)p)[x]);
|
||||
}
|
||||
printf("\n");
|
||||
printf("*RR* (read2() read %d bytes)\n", n);
|
||||
@ -877,7 +878,7 @@ void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
|
||||
* 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"
|
||||
@ -936,6 +937,7 @@ sam_sess_t *sam_session_init(sam_sess_t *session)
|
||||
SAMLOGS("Out of memory");
|
||||
abort();
|
||||
}
|
||||
session->child = NULL;
|
||||
}
|
||||
session->connected = false;
|
||||
session->prev_id = 0;
|
||||
@ -963,7 +965,7 @@ void sam_session_free(sam_sess_t **session)
|
||||
*
|
||||
* 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;
|
||||
@ -1080,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;
|
||||
@ -1103,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;
|
||||
@ -1141,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));
|
||||
@ -1399,7 +1386,7 @@ 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]))
|
||||
|
@ -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" />
|
||||
|
@ -220,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();
|
||||
}
|
||||
|
||||
|
@ -119,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;
|
||||
|
@ -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;
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,6 +453,8 @@ public class SAMStreamSession {
|
||||
} catch (I2PException e) {
|
||||
_log.debug("Caught I2PException", e);
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
_log.debug("Shutting down SAM STREAM session server");
|
||||
}
|
||||
@ -431,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;
|
||||
@ -442,44 +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);
|
||||
//i2pSocketOS.flush();
|
||||
} 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() {
|
||||
@ -538,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,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;
|
||||
|
||||
@ -44,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
|
||||
@ -68,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)) {
|
||||
@ -76,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 {
|
||||
@ -95,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 + "]");
|
||||
}
|
||||
buf.reset();
|
||||
|
||||
tok = new StringTokenizer(msg, " ");
|
||||
if (tok.countTokens() < 2) {
|
||||
@ -150,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 {
|
||||
@ -550,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;
|
||||
@ -564,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;
|
||||
}
|
||||
@ -707,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -742,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -801,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!");
|
||||
@ -811,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();
|
||||
}
|
||||
}
|
||||
}
|
@ -150,7 +150,7 @@ 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
|
||||
@ -217,7 +217,12 @@ public class TestStreamTransfer {
|
||||
}
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
|
||||
_log.info("Sending data");
|
||||
_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) {}
|
||||
|
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -32,7 +32,10 @@ public class Connection {
|
||||
private long _lastSendTime;
|
||||
private long _lastSendId;
|
||||
private boolean _resetReceived;
|
||||
private boolean _resetSent;
|
||||
private long _resetSentOn;
|
||||
private boolean _connected;
|
||||
private boolean _hardDisconnected;
|
||||
private MessageInputStream _inputStream;
|
||||
private MessageOutputStream _outputStream;
|
||||
private SchedulerChooser _chooser;
|
||||
@ -70,7 +73,7 @@ public class Connection {
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 60*1000;
|
||||
public static final long MIN_RESEND_DELAY = 40*1000;
|
||||
public static final long MIN_RESEND_DELAY = 30*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
public static int DISCONNECT_TIMEOUT = 5*60*1000;
|
||||
@ -111,7 +114,10 @@ public class Connection {
|
||||
_ackSinceCongestion = true;
|
||||
_connectLock = new Object();
|
||||
_activeResends = 0;
|
||||
_resetSentOn = -1;
|
||||
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.chokeSizeBegin", "How many messages were outstanding when we started to choke?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.chokeSizeEnd", "How many messages were outstanding when we stopped being choked?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
public long getNextOutboundPacketNum() {
|
||||
@ -135,25 +141,36 @@ public class Connection {
|
||||
boolean packetSendChoke(long timeoutMs) {
|
||||
if (false) return true;
|
||||
long writeExpire = timeoutMs;
|
||||
long start = _context.clock().now();
|
||||
boolean started = false;
|
||||
while (true) {
|
||||
long timeLeft = writeExpire - _context.clock().now();
|
||||
synchronized (_outboundPackets) {
|
||||
if (_outboundPackets.size() >= _options.getWindowSize()) {
|
||||
if (!started)
|
||||
_context.statManager().addRateData("stream.chokeSizeBegin", _outboundPackets.size(), timeoutMs);
|
||||
if (!_connected)
|
||||
return false;
|
||||
started = true;
|
||||
if ( (_outboundPackets.size() >= _options.getWindowSize()) || (_activeResends > 0) ) {
|
||||
if (writeExpire > 0) {
|
||||
if (timeLeft <= 0) {
|
||||
_log.error("Outbound window is full of " + _outboundPackets.size()
|
||||
+ " with " + _activeResends + " active resends"
|
||||
+ " and we've waited too long (" + writeExpire + "ms)");
|
||||
return false;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _options.getWindowSize() + "), waiting " + timeLeft);
|
||||
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _options.getWindowSize() + "/"
|
||||
+ _activeResends + "), waiting " + timeLeft);
|
||||
try { _outboundPackets.wait(timeLeft); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Outbound window is full (" + _outboundPackets.size() + "), waiting indefinitely");
|
||||
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends
|
||||
+ "), waiting indefinitely");
|
||||
try { _outboundPackets.wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} else {
|
||||
_context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -163,6 +180,25 @@ public class Connection {
|
||||
void ackImmediately() {
|
||||
_receiver.send(null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* got a packet we shouldn't have, send 'em a reset
|
||||
*
|
||||
*/
|
||||
void sendReset() {
|
||||
_resetSent = true;
|
||||
if (_resetSentOn <= 0)
|
||||
_resetSentOn = _context.clock().now();
|
||||
if ( (_remotePeer == null) || (_sendStreamId == null) ) return;
|
||||
PacketLocal reply = new PacketLocal(_context, _remotePeer);
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setSendStreamId(_sendStreamId);
|
||||
reply.setReceiveStreamId(_receiveStreamId);
|
||||
reply.setOptionalFrom(_connectionManager.getSession().getMyDestination());
|
||||
// this just sends the packet - no retries or whatnot
|
||||
_outboundQueue.enqueue(reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any data that we can
|
||||
@ -215,7 +251,7 @@ public class Connection {
|
||||
if (timeout > MAX_RESEND_DELAY)
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resend in " + timeout + " for " + packet);
|
||||
_log.debug("Resend in " + timeout + " for " + packet, new Exception("Sent by"));
|
||||
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
|
||||
}
|
||||
@ -296,8 +332,8 @@ public class Connection {
|
||||
_ackedPackets++;
|
||||
if (p.getNumSends() > 1) {
|
||||
_activeResends--;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Active resend of " + p + " successful, # active left: " + _activeResends);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Active resend of " + p + " successful, # active left: " + _activeResends);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -325,14 +361,19 @@ public class Connection {
|
||||
_occurredEventCount++;
|
||||
} else {
|
||||
_occurredTime = now;
|
||||
if (_occurredEventCount > 100) {
|
||||
_log.log(Log.CRIT, "More than 100 events (" + _occurredEventCount + ") in a second on "
|
||||
+ toString() + ": scheduler = " + sched);
|
||||
if ( (_occurredEventCount > 1000) && (_log.shouldLog(Log.WARN)) ) {
|
||||
_log.warn("More than 1000 events (" + _occurredEventCount + ") in a second on "
|
||||
+ toString() + ": scheduler = " + sched);
|
||||
}
|
||||
_occurredEventCount = 0;
|
||||
}
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
|
||||
sched.eventOccurred(this);
|
||||
long elapsed = System.currentTimeMillis() - before;
|
||||
if ( (elapsed > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took " + elapsed + "ms to pump through " + sched);
|
||||
}
|
||||
|
||||
void resetReceived() {
|
||||
@ -349,6 +390,9 @@ public class Connection {
|
||||
public boolean getResetReceived() { return _resetReceived; }
|
||||
|
||||
public boolean getIsConnected() { return _connected; }
|
||||
public boolean getHardDisconnected() { return _hardDisconnected; }
|
||||
public boolean getResetSent() { return _resetSent; }
|
||||
public long getResetSentOn() { return _resetSentOn; }
|
||||
|
||||
void disconnect(boolean cleanDisconnect) {
|
||||
disconnect(cleanDisconnect, true);
|
||||
@ -358,6 +402,13 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Disconnecting " + toString(), new Exception("discon"));
|
||||
|
||||
if (!cleanDisconnect) {
|
||||
_hardDisconnected = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Hard disconnecting and sending a reset on " + toString(), new Exception("cause"));
|
||||
sendReset();
|
||||
}
|
||||
|
||||
if (cleanDisconnect && _connected) {
|
||||
// send close packets and schedule stuff...
|
||||
_outputStream.closeInternal();
|
||||
@ -381,15 +432,12 @@ public class Connection {
|
||||
if (_socket != null)
|
||||
_socket.destroy();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
if (_outputStream != null)
|
||||
_outputStream.destroy();
|
||||
_outputStream = null;
|
||||
_outboundQueue = null;
|
||||
if (_receiver != null)
|
||||
_receiver.destroy();
|
||||
if (_activityTimer != null)
|
||||
SimpleTimer.getInstance().addEvent(_activityTimer, 1);
|
||||
SimpleTimer.getInstance().removeEvent(_activityTimer);
|
||||
_activityTimer = null;
|
||||
|
||||
if (!_disconnectScheduled) {
|
||||
@ -650,6 +698,14 @@ public class Connection {
|
||||
default:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Closing connection due to inactivity");
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("last sent was: ").append(_context.clock().now() - _lastSendTime);
|
||||
buf.append("ms ago, last received was: ").append(_context.clock().now()-_lastReceivedOn);
|
||||
buf.append("ms ago, inactivity timeout is: ").append(_options.getInactivityTimeout());
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
disconnect(true);
|
||||
break;
|
||||
}
|
||||
@ -683,6 +739,9 @@ public class Connection {
|
||||
buf.append(" wsize: ").append(_options.getWindowSize());
|
||||
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
|
||||
buf.append(" rtt: ").append(_options.getRTT());
|
||||
// not synchronized to avoid some kooky races
|
||||
buf.append(" unacked outbound: ").append(_outboundPackets.size()).append(" ");
|
||||
/*
|
||||
buf.append(" unacked outbound: ");
|
||||
synchronized (_outboundPackets) {
|
||||
buf.append(_outboundPackets.size()).append(" [");
|
||||
@ -691,6 +750,7 @@ public class Connection {
|
||||
}
|
||||
buf.append("] ");
|
||||
}
|
||||
*/
|
||||
buf.append("unacked inbound? ").append(getUnackedPacketsReceived());
|
||||
if (_inputStream != null) {
|
||||
buf.append(" [high ");
|
||||
@ -710,17 +770,16 @@ public class Connection {
|
||||
*/
|
||||
private class ResendPacketEvent implements SimpleTimer.TimedEvent {
|
||||
private PacketLocal _packet;
|
||||
private boolean _currentIsActiveResend;
|
||||
public ResendPacketEvent(PacketLocal packet) {
|
||||
_packet = packet;
|
||||
_currentIsActiveResend = false;
|
||||
packet.setResendPacketEvent(ResendPacketEvent.this);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
if (_packet.getAckTime() > 0)
|
||||
return;
|
||||
|
||||
if (!_connected) {
|
||||
if (_resetSent || _resetReceived) {
|
||||
_packet.cancelled();
|
||||
return;
|
||||
}
|
||||
@ -728,12 +787,15 @@ public class Connection {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Resend period reached for " + _packet);
|
||||
boolean resend = false;
|
||||
boolean isLowest = false;
|
||||
synchronized (_outboundPackets) {
|
||||
if (_packet.getSequenceNum() == _highestAckedThrough + 1)
|
||||
isLowest = true;
|
||||
if (_outboundPackets.containsKey(new Long(_packet.getSequenceNum())))
|
||||
resend = true;
|
||||
}
|
||||
if ( (resend) && (_packet.getAckTime() < 0) ) {
|
||||
if ( (_activeResends > 0) && (!_currentIsActiveResend) ) {
|
||||
if (!isLowest) {
|
||||
// we want to resend this packet, but there are already active
|
||||
// resends in the air and we dont want to make a bad situation
|
||||
// worse. wait another second
|
||||
@ -765,7 +827,6 @@ public class Connection {
|
||||
if (numSends == 2) {
|
||||
// first resend for this packet
|
||||
_activeResends++;
|
||||
_currentIsActiveResend = true;
|
||||
}
|
||||
|
||||
// in case things really suck, the other side may have lost thier
|
||||
@ -785,6 +846,17 @@ public class Connection {
|
||||
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
|
||||
_outboundQueue.enqueue(_packet);
|
||||
|
||||
_lastSendTime = _context.clock().now();
|
||||
|
||||
// acked during resending (... or somethin')
|
||||
if ( (_packet.getAckTime() > 0) && (_packet.getNumSends() > 1) ) {
|
||||
_activeResends--;
|
||||
synchronized (_outboundPackets) {
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (numSends > _options.getMaxResends()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Too many resends");
|
||||
|
@ -17,13 +17,20 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Connection _connection;
|
||||
private MessageOutputStream.WriteStatus _dummyStatus;
|
||||
private static final MessageOutputStream.WriteStatus _dummyStatus = new DummyStatus();
|
||||
|
||||
public ConnectionDataReceiver(I2PAppContext ctx, Connection con) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(ConnectionDataReceiver.class);
|
||||
_connection = con;
|
||||
_dummyStatus = new DummyStatus();
|
||||
}
|
||||
|
||||
public boolean writeInProcess() {
|
||||
Connection con = _connection;
|
||||
if (con != null)
|
||||
return con.getUnackedPacketsSent() > 0;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,10 +45,12 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* delivery.
|
||||
*/
|
||||
public MessageOutputStream.WriteStatus writeData(byte[] buf, int off, int size) {
|
||||
Connection con = _connection;
|
||||
if (con == null) return _dummyStatus;
|
||||
boolean doSend = true;
|
||||
if ( (size <= 0) && (_connection.getLastSendId() >= 0) ) {
|
||||
if (_connection.getOutputStream().getClosed()) {
|
||||
if (_connection.getCloseSentOn() < 0) {
|
||||
if ( (size <= 0) && (con.getLastSendId() >= 0) ) {
|
||||
if (con.getOutputStream().getClosed()) {
|
||||
if (con.getCloseSentOn() < 0) {
|
||||
doSend = true;
|
||||
} else {
|
||||
// closed, no new data, and we've already sent a close packet
|
||||
@ -53,18 +62,20 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
if (_connection.getUnackedPacketsReceived() > 0)
|
||||
if (con.getUnackedPacketsReceived() > 0)
|
||||
doSend = true;
|
||||
|
||||
if (_log.shouldLog(Log.INFO) && !doSend)
|
||||
_log.info("writeData called: size="+size + " doSend=" + doSend
|
||||
+ " unackedReceived: " + _connection.getUnackedPacketsReceived()
|
||||
+ " con: " + _connection, new Exception("write called by"));
|
||||
+ " unackedReceived: " + con.getUnackedPacketsReceived()
|
||||
+ " con: " + con, new Exception("write called by"));
|
||||
|
||||
if (doSend) {
|
||||
PacketLocal packet = send(buf, off, size);
|
||||
if (packet == null) return _dummyStatus;
|
||||
|
||||
//dont wait for non-acks
|
||||
if ( (packet.getPayloadSize() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
|
||||
return packet;
|
||||
else
|
||||
return _dummyStatus;
|
||||
@ -81,7 +92,7 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* @param buf data to be sent - may be null
|
||||
* @param off offset into the buffer to start writing from
|
||||
* @param size how many bytes of the buffer to write (may be 0)
|
||||
* @return the packet sent
|
||||
* @return the packet sent, or null if the connection died
|
||||
*/
|
||||
public PacketLocal send(byte buf[], int off, int size) {
|
||||
return send(buf, off, size, false);
|
||||
@ -95,23 +106,33 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* @return the packet sent
|
||||
*/
|
||||
public PacketLocal send(byte buf[], int off, int size, boolean forceIncrement) {
|
||||
PacketLocal packet = buildPacket(buf, off, size, forceIncrement);
|
||||
_connection.sendPacket(packet);
|
||||
Connection con = _connection;
|
||||
if (con == null) return null;
|
||||
long before = System.currentTimeMillis();
|
||||
PacketLocal packet = buildPacket(con, buf, off, size, forceIncrement);
|
||||
long built = System.currentTimeMillis();
|
||||
con.sendPacket(packet);
|
||||
long sent = System.currentTimeMillis();
|
||||
|
||||
if ( (built-before > 5*1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, took " + (built-before) + "ms to build a packet: " + packet);
|
||||
if ( (sent-built> 5*1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, took " + (sent-built) + "ms to send a packet: " + packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private boolean isAckOnly(int size) {
|
||||
private boolean isAckOnly(Connection con, int size) {
|
||||
boolean ackOnly = ( (size <= 0) && // no data
|
||||
(_connection.getLastSendId() >= 0) && // not a SYN
|
||||
( (!_connection.getOutputStream().getClosed()) || // not a CLOSE
|
||||
(_connection.getOutputStream().getClosed() &&
|
||||
_connection.getCloseSentOn() > 0) )); // or it is a dup CLOSE
|
||||
(con.getLastSendId() >= 0) && // not a SYN
|
||||
( (!con.getOutputStream().getClosed()) || // not a CLOSE
|
||||
(con.getOutputStream().getClosed() &&
|
||||
con.getCloseSentOn() > 0) )); // or it is a dup CLOSE
|
||||
return ackOnly;
|
||||
}
|
||||
|
||||
private PacketLocal buildPacket(byte buf[], int off, int size, boolean forceIncrement) {
|
||||
boolean ackOnly = isAckOnly(size);
|
||||
PacketLocal packet = new PacketLocal(_context, _connection.getRemotePeer(), _connection);
|
||||
private PacketLocal buildPacket(Connection con, byte buf[], int off, int size, boolean forceIncrement) {
|
||||
boolean ackOnly = isAckOnly(con, size);
|
||||
PacketLocal packet = new PacketLocal(_context, con.getRemotePeer(), con);
|
||||
byte data[] = new byte[size];
|
||||
if (size > 0)
|
||||
System.arraycopy(buf, off, data, 0, size);
|
||||
@ -119,36 +140,36 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
if (ackOnly && !forceIncrement)
|
||||
packet.setSequenceNum(0);
|
||||
else
|
||||
packet.setSequenceNum(_connection.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(_connection.getSendStreamId());
|
||||
packet.setReceiveStreamId(_connection.getReceiveStreamId());
|
||||
packet.setSequenceNum(con.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(con.getSendStreamId());
|
||||
packet.setReceiveStreamId(con.getReceiveStreamId());
|
||||
|
||||
_connection.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(_connection.getOptions().getChoke());
|
||||
packet.setOptionalMaxSize(_connection.getOptions().getMaxMessageSize());
|
||||
packet.setResendDelay(_connection.getOptions().getResendDelay());
|
||||
con.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(con.getOptions().getChoke());
|
||||
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
|
||||
packet.setResendDelay(con.getOptions().getResendDelay());
|
||||
|
||||
if (_connection.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
if (con.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, true);
|
||||
else
|
||||
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, false);
|
||||
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED, _connection.getOptions().getRequireFullySigned());
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED, con.getOptions().getRequireFullySigned());
|
||||
|
||||
if ( (!ackOnly) && (packet.getSequenceNum() <= 0) ) {
|
||||
packet.setFlag(Packet.FLAG_SYNCHRONIZE);
|
||||
packet.setOptionalFrom(_connection.getSession().getMyDestination());
|
||||
packet.setOptionalFrom(con.getSession().getMyDestination());
|
||||
}
|
||||
|
||||
// don't set the closed flag if this is a plain ACK and there are outstanding
|
||||
// packets sent, otherwise the other side could receive the CLOSE prematurely,
|
||||
// since this ACK could arrive before the unacked payload message.
|
||||
if (_connection.getOutputStream().getClosed() &&
|
||||
( (size > 0) || (_connection.getUnackedPacketsSent() <= 0) ) ) {
|
||||
if (con.getOutputStream().getClosed() &&
|
||||
( (size > 0) || (con.getUnackedPacketsSent() <= 0) || (packet.getSequenceNum() > 0) ) ) {
|
||||
packet.setFlag(Packet.FLAG_CLOSE);
|
||||
_connection.setCloseSentOn(_context.clock().now());
|
||||
con.setCloseSentOn(_context.clock().now());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closed is set for a new packet on " + _connection + ": " + packet);
|
||||
_log.debug("Closed is set for a new packet on " + con + ": " + packet);
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Closed is not set for a new packet on " + _connection + ": " + packet);
|
||||
|
@ -366,7 +366,8 @@ public class ConnectionManager {
|
||||
if (removed) {
|
||||
if (_notifier != null)
|
||||
_notifier.pingComplete(false);
|
||||
_log.error("Ping failed");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ping failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,8 +87,10 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_SEND));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
|
||||
setInboundBufferSize((getMaxMessageSize() + 2) * Connection.MAX_WINDOW_SIZE);
|
||||
|
||||
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,17 +25,43 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().createRateStat("stream.con.receiveDuplicateSize", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.resetReceived", "How many messages had we sent successfully before receiving a RESET?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
/** distribute a packet to the connection specified */
|
||||
void receivePacket(Packet packet, Connection con) throws I2PException {
|
||||
boolean ok = verifyPacket(packet, con);
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
if ( (!packet.isFlagSet(Packet.FLAG_RESET)) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error("Packet does NOT verify: " + packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (con.getHardDisconnected()) {
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ||
|
||||
(packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) || (packet.isFlagSet(Packet.FLAG_CLOSE)) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a data packet after hard disconnect: " + packet + " on " + con);
|
||||
con.sendReset();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a packet after hard disconnect, ignoring: " + packet + " on " + con);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
con.packetReceived();
|
||||
|
||||
if (con.getInputStream().getTotalQueuedSize() > con.getOptions().getInboundBufferSize()) {
|
||||
long ready = con.getInputStream().getHighestReadyBockId();
|
||||
int available = con.getOptions().getInboundBufferSize() - con.getInputStream().getTotalReadySize();
|
||||
int allowedBlocks = available/con.getOptions().getMaxMessageSize();
|
||||
if ( (packet.getPayloadSize() > 0) && (packet.getSequenceNum() > ready + allowedBlocks) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Inbound buffer exceeded on connection " + con + ": dropping " + packet);
|
||||
_log.warn("Inbound buffer exceeded on connection " + con + " ("
|
||||
+ ready + "/"+ (ready+allowedBlocks) + "/" + available
|
||||
+ ": dropping " + packet);
|
||||
ack(con, packet.getAckThrough(), packet.getNacks(), null, false);
|
||||
con.getOptions().setChoke(5*1000);
|
||||
return;
|
||||
}
|
||||
@ -55,6 +81,8 @@ public class ConnectionPacketHandler {
|
||||
if (packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED))
|
||||
con.closeReceived();
|
||||
|
||||
boolean fastAck = false;
|
||||
|
||||
if (isNew) {
|
||||
con.incrementUnackedPacketsReceived();
|
||||
con.incrementBytesReceived(packet.getPayloadSize());
|
||||
@ -72,18 +100,17 @@ public class ConnectionPacketHandler {
|
||||
_log.debug("Scheduling ack in " + delay + "ms for received packet " + packet);
|
||||
}
|
||||
} else {
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ) {
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ||
|
||||
(packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) {
|
||||
_context.statManager().addRateData("stream.con.receiveDuplicateSize", packet.getPayloadSize(), 0);
|
||||
con.incrementDupMessagesReceived(1);
|
||||
|
||||
// take note of congestion
|
||||
//con.getOptions().setResendDelay(con.getOptions().getResendDelay()*2);
|
||||
//con.getOptions().setWindowSize(con.getOptions().getWindowSize()/2);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("congestion.. dup " + packet);
|
||||
SimpleTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
|
||||
//con.incrementUnackedPacketsReceived();
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
//con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
//fastAck = true;
|
||||
} else {
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
//con.incrementUnackedPacketsReceived();
|
||||
@ -95,8 +122,20 @@ public class ConnectionPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fastAck = fastAck || ack(con, packet.getAckThrough(), packet.getNacks(), packet, isNew);
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + 2000 < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew) {
|
||||
int numResends = 0;
|
||||
List acked = con.ackPackets(packet.getAckThrough(), packet.getNacks());
|
||||
List acked = con.ackPackets(ackThrough, nacks);
|
||||
if ( (acked != null) && (acked.size() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(acked.size() + " of our packets acked with " + packet);
|
||||
@ -130,18 +169,15 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().addRateData("stream.con.packetsAckedPerMessageReceived", acked.size(), highestRTT);
|
||||
}
|
||||
|
||||
boolean fastAck = adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + con.getOptions().getRTT() < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
if (packet != null)
|
||||
return adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
else
|
||||
return adjustWindow(con, false, -1, numResends, (acked != null ? acked.size() : 0));
|
||||
}
|
||||
|
||||
|
||||
private boolean adjustWindow(Connection con, boolean isNew, long sequenceNum, int numResends, int acked) {
|
||||
boolean congested = false;
|
||||
if ( (!isNew) && (sequenceNum > 0) ) {
|
||||
// dup real packet
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
@ -156,64 +192,38 @@ public class ConnectionPacketHandler {
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
|
||||
return true;
|
||||
//} else if (numResends > 0) {
|
||||
// window sizes are shrunk on resend, not on ack
|
||||
} else {
|
||||
if (acked > 0) {
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
|
||||
if (numResends <= 0) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
congested = true;
|
||||
}
|
||||
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
// integers, so lets use a random distribution instead
|
||||
int shouldIncrement = _context.random().nextInt(newWindowSize);
|
||||
if (shouldIncrement <= 0)
|
||||
newWindowSize += 1;
|
||||
} else {
|
||||
// slow start
|
||||
newWindowSize += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
// integers, so lets use a random distribution instead
|
||||
int shouldIncrement = _context.random().nextInt(newWindowSize);
|
||||
if (shouldIncrement <= 0)
|
||||
newWindowSize += 1;
|
||||
} else {
|
||||
// slow start
|
||||
newWindowSize += 1;
|
||||
}
|
||||
} else {
|
||||
// received a message that doesn't contain a new ack
|
||||
|
||||
// ehh. cant do this, as we SACK and the acks may be
|
||||
// received out of order:
|
||||
// Alice: RECEIVE 2
|
||||
// Alice: SEND ack 2 nack 1
|
||||
// Alice: RECEIVE 1
|
||||
// Alice: SEND ack 2
|
||||
// Bob: RECEIVE ack 2
|
||||
// Bob: RECEIVE ack 2 nack 1 <-- NOT bad
|
||||
|
||||
/*
|
||||
if (con.getUnackedPacketsSent() > 0) {
|
||||
// peer got a dup
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
oldSize >>>= 1;
|
||||
if (oldSize <= 0)
|
||||
oldSize = 1;
|
||||
con.getOptions().setWindowSize(oldSize);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
}
|
||||
return false;
|
||||
|
||||
return congested;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -271,6 +281,8 @@ public class ConnectionPacketHandler {
|
||||
con.resetReceived();
|
||||
con.eventOccurred();
|
||||
|
||||
_context.statManager().addRateData("stream.resetReceived", con.getHighestAckedThrough(), con.getLifetime());
|
||||
|
||||
// no further processing
|
||||
return;
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ public class I2PSocketFull implements I2PSocket {
|
||||
destroy();
|
||||
}
|
||||
|
||||
Connection getConnection() { return _connection; }
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return _connection.getInputStream();
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ 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;
|
||||
|
||||
@ -225,4 +227,12 @@ public class I2PSocketManagerFull implements I2PSocketManager {
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
|
||||
|
||||
public void addDisconnectListener(DisconnectListener lsnr) {
|
||||
_connectionManager.getMessageHandler().addDisconnectListener(lsnr);
|
||||
}
|
||||
public void removeDisconnectListener(DisconnectListener lsnr) {
|
||||
_connectionManager.getMessageHandler().removeDisconnectListener(lsnr);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
@ -15,10 +18,12 @@ public class MessageHandler implements I2PSessionListener {
|
||||
private ConnectionManager _manager;
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private List _listeners;
|
||||
|
||||
public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) {
|
||||
_manager = mgr;
|
||||
_context = ctx;
|
||||
_listeners = new ArrayList(1);
|
||||
_log = ctx.logManager().getLog(MessageHandler.class);
|
||||
_context.statManager().createRateStat("stream.packetReceiveFailure", "When do we fail to decrypt or otherwise receive a packet sent to us?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
@ -69,6 +74,16 @@ public class MessageHandler implements I2PSessionListener {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("I2PSession disconnected");
|
||||
_manager.disconnectAllHard();
|
||||
|
||||
List listeners = null;
|
||||
synchronized (_listeners) {
|
||||
listeners = new ArrayList(_listeners);
|
||||
_listeners.clear();
|
||||
}
|
||||
for (int i = 0; i < listeners.size(); i++) {
|
||||
I2PSocketManager.DisconnectListener lsnr = (I2PSocketManager.DisconnectListener)listeners.get(i);
|
||||
lsnr.sessionDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,4 +95,15 @@ public class MessageHandler implements I2PSessionListener {
|
||||
_log.error("error occurred: " + message, error);
|
||||
//_manager.disconnectAllHard();
|
||||
}
|
||||
|
||||
public void addDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
|
||||
synchronized (_listeners) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
}
|
||||
public void removeDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
|
||||
synchronized (_listeners) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ public class MessageInputStream extends InputStream {
|
||||
private List _readyDataBlocks;
|
||||
private int _readyDataBlockIndex;
|
||||
/** highest message ID used in the readyDataBlocks */
|
||||
private long _highestReadyBlockId;
|
||||
private volatile long _highestReadyBlockId;
|
||||
/** highest overall message ID */
|
||||
private long _highestBlockId;
|
||||
private volatile long _highestBlockId;
|
||||
/**
|
||||
* Message ID (Long) to ByteArray for blocks received
|
||||
* out of order when there are lower IDs not yet
|
||||
@ -74,15 +74,13 @@ public class MessageInputStream extends InputStream {
|
||||
|
||||
/** What is the highest block ID we've completely received through? */
|
||||
public long getHighestReadyBockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
// not synchronized as it doesnt hurt to read a too-low value
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
|
||||
public long getHighestBlockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestBlockId;
|
||||
}
|
||||
// not synchronized as it doesnt hurt to read a too-low value
|
||||
return _highestBlockId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,6 +392,21 @@ public class MessageInputStream extends InputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalReadySize() {
|
||||
synchronized (_dataLock) {
|
||||
if (_locallyClosed) return 0;
|
||||
int numBytes = 0;
|
||||
for (int i = 0; i < _readyDataBlocks.size(); i++) {
|
||||
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
|
||||
if (i == 0)
|
||||
numBytes += cur.getData().length - _readyDataBlockIndex;
|
||||
else
|
||||
numBytes += cur.getData().length;
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (_dataLock) {
|
||||
_readyDataBlocks.clear();
|
||||
|
@ -47,7 +47,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
_written = 0;
|
||||
_closed = false;
|
||||
_writeTimeout = -1;
|
||||
_passiveFlushDelay = 5*1000;
|
||||
_passiveFlushDelay = 500;
|
||||
_flusher = new Flusher();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("MessageOutputStream created");
|
||||
@ -83,8 +83,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
remaining = 0;
|
||||
_lastBuffered = _context.clock().now();
|
||||
if (_passiveFlushDelay > 0) {
|
||||
// if it is already enqueued, this just pushes it further out
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
_flusher.enqueue();
|
||||
}
|
||||
} else {
|
||||
// buffer whatever we can fit then flush,
|
||||
@ -115,9 +114,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (!ws.writeAccepted()) {
|
||||
if (_writeTimeout > 0)
|
||||
throw new InterruptedIOException("Write not accepted within timeout");
|
||||
throw new InterruptedIOException("Write not accepted within timeout: " + ws);
|
||||
else
|
||||
throw new IOException("Write not accepted into the queue");
|
||||
throw new IOException("Write not accepted into the queue: " + ws);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -140,11 +139,42 @@ public class MessageOutputStream extends OutputStream {
|
||||
* period of inactivity
|
||||
*/
|
||||
private class Flusher implements SimpleTimer.TimedEvent {
|
||||
private boolean _enqueued;
|
||||
public void enqueue() {
|
||||
// no need to be overly worried about duplicates - it would just
|
||||
// push it further out
|
||||
if (!_enqueued) {
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("NOT enqueing the flusher");
|
||||
}
|
||||
_enqueued = true;
|
||||
}
|
||||
public void timeReached() {
|
||||
_enqueued = false;
|
||||
DataReceiver rec = _dataReceiver;
|
||||
long timeLeft = (_lastBuffered + _passiveFlushDelay - _context.clock().now());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("flusher time reached: left = " + timeLeft);
|
||||
if (timeLeft > 0)
|
||||
enqueue();
|
||||
else if ( (rec != null) && (rec.writeInProcess()) )
|
||||
enqueue(); // don't passive flush if there is a write being done (unacked outbound)
|
||||
else
|
||||
doFlush();
|
||||
}
|
||||
|
||||
private void doFlush() {
|
||||
boolean sent = false;
|
||||
WriteStatus ws = null;
|
||||
synchronized (_dataLock) {
|
||||
if ( (_valid > 0) && (_lastBuffered + _passiveFlushDelay > _context.clock().now()) ) {
|
||||
long flushTime = _lastBuffered + _passiveFlushDelay;
|
||||
if ( (_valid > 0) && (flushTime < _context.clock().now()) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("doFlush() valid = " + _valid);
|
||||
if ( (_buf != null) && (_dataReceiver != null) ) {
|
||||
ws = _dataReceiver.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
@ -153,13 +183,15 @@ public class MessageOutputStream extends OutputStream {
|
||||
_dataLock.notifyAll();
|
||||
sent = true;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("doFlush() rejected... valid = " + _valid);
|
||||
}
|
||||
}
|
||||
// ignore the ws
|
||||
if (sent && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Passive flush of " + ws);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,6 +307,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
}
|
||||
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
|
||||
WriteStatus ws = null;
|
||||
long before = System.currentTimeMillis();
|
||||
synchronized (_dataLock) {
|
||||
// _buf may be null, but the data receiver can handle that just fine,
|
||||
// deciding whether or not to send a packet
|
||||
@ -284,6 +317,10 @@ public class MessageOutputStream extends OutputStream {
|
||||
_dataLock.notifyAll();
|
||||
_lastFlushed = _context.clock().now();
|
||||
}
|
||||
long afterBuild = System.currentTimeMillis();
|
||||
if ( (afterBuild - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (afterBuild-before) + "ms to build a packet? " + ws);
|
||||
|
||||
if (blocking && ws != null) {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (ws.writeFailed())
|
||||
@ -291,6 +328,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
else if (!ws.writeAccepted())
|
||||
throw new InterruptedIOException("Flush available timed out");
|
||||
}
|
||||
long afterAccept = System.currentTimeMillis();
|
||||
if ( (afterAccept - afterBuild > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (afterAccept-afterBuild) + "ms to accept a packet? " + ws);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -304,6 +344,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
* Nonblocking write
|
||||
*/
|
||||
public WriteStatus writeData(byte buf[], int off, int size);
|
||||
public boolean writeInProcess();
|
||||
}
|
||||
|
||||
/** Define a way to detect the status of a write */
|
||||
|
@ -104,7 +104,7 @@ public class PacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
void displayPacket(Packet packet, String prefix) {
|
||||
String msg = null;
|
||||
synchronized (_fmt) {
|
||||
@ -152,16 +152,28 @@ public class PacketHandler {
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Receive a syn packet with the wrong IDs: " + packet);
|
||||
_log.warn("Receive a syn packet with the wrong IDs, sending reset: " + packet);
|
||||
sendReset(packet);
|
||||
}
|
||||
} else {
|
||||
// someone is sending us a packet on the wrong stream
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a packet on the wrong stream: " + packet);
|
||||
_log.warn("Received a packet on the wrong stream: " + packet + " connection: " + con);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendReset(Packet packet) {
|
||||
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom());
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setSendStreamId(packet.getReceiveStreamId());
|
||||
reply.setReceiveStreamId(packet.getSendStreamId());
|
||||
reply.setOptionalFrom(_manager.getSession().getMyDestination());
|
||||
// this just sends the packet - no retries or whatnot
|
||||
_manager.getPacketQueue().enqueue(reply);
|
||||
}
|
||||
|
||||
private void receiveUnknownCon(Packet packet, byte sendId[]) {
|
||||
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
|
||||
if (packet.getSendStreamId() != null) {
|
||||
|
@ -5,6 +5,8 @@ import java.util.Set;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* coordinate local attributes about a packet - send time, ack time, number of
|
||||
@ -12,6 +14,7 @@ import net.i2p.data.SessionKey;
|
||||
*/
|
||||
public class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Connection _connection;
|
||||
private Destination _to;
|
||||
private SessionKey _keyUsed;
|
||||
@ -22,6 +25,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
private long _acceptedOn;
|
||||
private long _ackOn;
|
||||
private long _cancelledOn;
|
||||
private SimpleTimer.TimedEvent _resendEvent;
|
||||
|
||||
public PacketLocal(I2PAppContext ctx, Destination to) {
|
||||
this(ctx, to, null);
|
||||
@ -29,6 +33,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public PacketLocal(I2PAppContext ctx, Destination to, Connection con) {
|
||||
_context = ctx;
|
||||
_createdOn = ctx.clock().now();
|
||||
_log = ctx.logManager().getLog(PacketLocal.class);
|
||||
_to = to;
|
||||
_connection = con;
|
||||
_lastSend = -1;
|
||||
@ -64,6 +69,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public void prepare() {
|
||||
if (_connection != null)
|
||||
_connection.getInputStream().updateAcks(this);
|
||||
if (_numSends > 0) // so we can debug to differentiate resends
|
||||
setOptionalDelay(_numSends * 1000);
|
||||
}
|
||||
|
||||
public long getCreatedOn() { return _createdOn; }
|
||||
@ -78,14 +85,16 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
_ackOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
_connection = null;
|
||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
||||
}
|
||||
public void cancelled() {
|
||||
synchronized (this) {
|
||||
_cancelledOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
_connection = null;
|
||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
|
||||
}
|
||||
|
||||
/** how long after packet creation was it acked? */
|
||||
@ -99,28 +108,44 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public long getLastSend() { return _lastSend; }
|
||||
public Connection getConnection() { return _connection; }
|
||||
|
||||
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
|
||||
|
||||
public String toString() {
|
||||
String str = super.toString();
|
||||
|
||||
if ( (_tagsSent != null) && (_tagsSent.size() > 0) )
|
||||
str = str + " with tags";
|
||||
|
||||
if (_ackOn > 0)
|
||||
return str + " ack after " + getAckTime();
|
||||
return str + " ack after " + getAckTime() + (_numSends <= 1 ? "" : " sent " + _numSends + " times");
|
||||
else
|
||||
return str;
|
||||
return str + (_numSends <= 1 ? "" : " sent " + _numSends + " times");
|
||||
}
|
||||
|
||||
public void waitForAccept(int maxWaitMs) {
|
||||
if (_connection == null)
|
||||
throw new IllegalStateException("Cannot wait for accept with no connection");
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
long before = _context.clock().now();
|
||||
long expiration = before+maxWaitMs;
|
||||
int queued = _connection.getUnackedPacketsSent();
|
||||
int window = _connection.getOptions().getWindowSize();
|
||||
boolean accepted = _connection.packetSendChoke(maxWaitMs);
|
||||
long after = _context.clock().now();
|
||||
if (accepted)
|
||||
_acceptedOn = _context.clock().now();
|
||||
_acceptedOn = after;
|
||||
else
|
||||
_acceptedOn = -1;
|
||||
_connection = null;
|
||||
int afterQueued = _connection.getUnackedPacketsSent();
|
||||
if ( (after - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (after-before) + "ms to get "
|
||||
+ (accepted ? " accepted" : " rejected")
|
||||
+ (_cancelledOn > 0 ? " and CANCELLED" : "")
|
||||
+ ", queued behind " + queued +" with a window size of " + window
|
||||
+ ", finally accepted with " + afterQueued + " queued: "
|
||||
+ toString());
|
||||
}
|
||||
|
||||
public void waitForCompletion(int maxWaitMs) {
|
||||
_connection = null;
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
while (true) {
|
||||
long timeRemaining = expiration - _context.clock().now();
|
||||
|
@ -24,24 +24,17 @@ class PacketQueue {
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private ConnectionManager _connectionManager;
|
||||
private byte _buf[];
|
||||
private ByteCache _cache = ByteCache.getInstance(64, 36*1024);
|
||||
|
||||
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
|
||||
_context = context;
|
||||
_session = session;
|
||||
_connectionManager = mgr;
|
||||
_buf = _cache.acquire().getData(); // new byte[36*1024];
|
||||
_log = context.logManager().getLog(PacketQueue.class);
|
||||
_context.statManager().createRateStat("stream.con.sendMessageSize", "Size of a message sent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.con.sendDuplicateSize", "Size of a message resent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
_cache.release(new ByteArray(_buf));
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new packet to be sent out ASAP
|
||||
*/
|
||||
@ -53,7 +46,7 @@ class PacketQueue {
|
||||
keyUsed = new SessionKey();
|
||||
Set tagsSent = packet.getTagsSent();
|
||||
if (tagsSent == null)
|
||||
tagsSent = new HashSet();
|
||||
tagsSent = new HashSet(0);
|
||||
|
||||
// cache this from before sendMessage
|
||||
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
|
||||
@ -63,29 +56,39 @@ class PacketQueue {
|
||||
} else {
|
||||
_log.debug("Sending... " + packet);
|
||||
}
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte buf[] = ba.getData();
|
||||
|
||||
long begin = 0;
|
||||
long end = 0;
|
||||
boolean sent = false;
|
||||
try {
|
||||
int size = 0;
|
||||
synchronized (this) {
|
||||
Arrays.fill(_buf, (byte)0x0);
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(_buf, 0);
|
||||
long beforeWrite = System.currentTimeMillis();
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(buf, 0);
|
||||
long writeTime = System.currentTimeMillis() - beforeWrite;
|
||||
if ( (writeTime > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("took " + writeTime + "ms to write the packet: " + packet);
|
||||
|
||||
// this should not block!
|
||||
begin = _context.clock().now();
|
||||
sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
|
||||
end = _context.clock().now();
|
||||
}
|
||||
// last chance to short circuit...
|
||||
if (packet.getAckTime() > 0) return;
|
||||
|
||||
// this should not block!
|
||||
begin = _context.clock().now();
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent);
|
||||
end = _context.clock().now();
|
||||
|
||||
if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took " + (end-begin) + "ms to sendMessage(...) " + packet);
|
||||
|
||||
_context.statManager().addRateData("stream.con.sendMessageSize", size, packet.getLifetime());
|
||||
if (packet.getNumSends() > 1)
|
||||
_context.statManager().addRateData("stream.con.sendDuplicateSize", size, packet.getLifetime());
|
||||
|
||||
|
||||
Connection con = packet.getConnection();
|
||||
if (con != null) {
|
||||
con.incrementBytesSent(size);
|
||||
@ -97,6 +100,8 @@ class PacketQueue {
|
||||
_log.warn("Unable to send the packet " + packet, ise);
|
||||
}
|
||||
|
||||
_cache.release(ba);
|
||||
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Send failed for " + packet);
|
||||
|
@ -38,6 +38,7 @@ class SchedulerChooser {
|
||||
|
||||
private List createSchedulers() {
|
||||
List rv = new ArrayList(8);
|
||||
rv.add(new SchedulerHardDisconnected(_context));
|
||||
rv.add(new SchedulerPreconnect(_context));
|
||||
rv.add(new SchedulerConnecting(_context));
|
||||
rv.add(new SchedulerReceived(_context));
|
||||
@ -54,8 +55,7 @@ class SchedulerChooser {
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Event occurred on " + con, new Exception("source"));
|
||||
_log.log(Log.CRIT, "Yell at jrandom: Event occurred on " + con, new Exception("source"));
|
||||
}
|
||||
public boolean accept(Connection con) { return true; }
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ class SchedulerConnecting extends SchedulerImpl {
|
||||
public boolean accept(Connection con) {
|
||||
if (con == null) return false;
|
||||
boolean notYetConnected = (con.getIsConnected()) &&
|
||||
(con.getSendStreamId() == null) &&
|
||||
//(con.getSendStreamId() == null) && // not null on recv
|
||||
(con.getLastSendId() >= 0) &&
|
||||
(con.getAckedPackets() <= 0) &&
|
||||
(!con.getResetReceived());
|
||||
@ -55,6 +55,7 @@ class SchedulerConnecting extends SchedulerImpl {
|
||||
_log.debug("waited too long: " + waited);
|
||||
return;
|
||||
} else {
|
||||
// should we be doing a con.sendAvailable here?
|
||||
if (con.getOptions().getConnectTimeout() > 0)
|
||||
reschedule(con.getOptions().getConnectTimeout(), con);
|
||||
}
|
||||
|
@ -33,10 +33,13 @@ class SchedulerDead extends SchedulerImpl {
|
||||
public boolean accept(Connection con) {
|
||||
if (con == null) return false;
|
||||
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
|
||||
if (con.getResetSent())
|
||||
timeSinceClose = _context.clock().now() - con.getResetSentOn();
|
||||
boolean nothingLeftToDo = (con.getCloseSentOn() > 0) &&
|
||||
(con.getCloseReceivedOn() > 0) &&
|
||||
(con.getUnackedPacketsReceived() <= 0) &&
|
||||
(con.getUnackedPacketsSent() <= 0) &&
|
||||
(con.getResetSent()) &&
|
||||
(timeSinceClose >= Connection.DISCONNECT_TIMEOUT);
|
||||
boolean timedOut = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
|
||||
con.getSendStreamId() == null &&
|
||||
|
@ -0,0 +1,47 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used after we've locally done a hard disconnect,
|
||||
* but the final timeout hasn't passed.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Locally disconnected hard.</li>
|
||||
* <li>Less than the final timeout period has passed since the last ACK.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Packets received</li>
|
||||
* <li>RESET received</li>
|
||||
* <li>Message sending fails (error talking to the session)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerDead dead} - after the final timeout passes</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
*/
|
||||
class SchedulerHardDisconnected extends SchedulerImpl {
|
||||
private Log _log;
|
||||
public SchedulerHardDisconnected(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerHardDisconnected.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
if (con == null) return false;
|
||||
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
|
||||
if (con.getResetSent())
|
||||
timeSinceClose = _context.clock().now() - con.getResetSentOn();
|
||||
boolean ok = (con.getHardDisconnected() || con.getResetSent()) &&
|
||||
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
// noop. we do the timeout through the simpleTimer anyway
|
||||
}
|
||||
}
|
@ -32,5 +32,6 @@ abstract class SchedulerImpl implements TaskScheduler {
|
||||
// _log.debug("firing event on " + _connection, _addedBy);
|
||||
_connection.eventOccurred();
|
||||
}
|
||||
public String toString() { return "event on " + _connection; }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,138 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Have a client connect to a server, where the server waits 5
|
||||
* seconds and closes the socket and the client detect that
|
||||
* EOF.
|
||||
*
|
||||
*/
|
||||
public class ConnectCloseTest {
|
||||
private Log _log;
|
||||
private I2PSession _server;
|
||||
public void test() {
|
||||
try {
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
_log = context.logManager().getLog(ConnectCloseTest.class);
|
||||
_log.debug("creating server session");
|
||||
_server = createSession();
|
||||
_log.debug("running server");
|
||||
runServer(context, _server);
|
||||
_log.debug("running client");
|
||||
runClient(context, createSession());
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(10*60*1000); } catch (Exception e) {}
|
||||
}
|
||||
|
||||
private void runClient(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ClientRunner(ctx, session));
|
||||
t.setName("client");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void runServer(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ServerRunner(ctx, session));
|
||||
t.setName("server");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private class ServerRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ServerRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ServerRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("* manager created");
|
||||
I2PServerSocket ssocket = mgr.getServerSocket();
|
||||
_log.debug("* server socket created");
|
||||
while (true) {
|
||||
I2PSocket socket = ssocket.accept();
|
||||
_log.debug("* socket accepted: " + socket);
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
socket.close();
|
||||
_log.debug("* socket closed: " + socket);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ClientRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ClientRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("* manager created");
|
||||
I2PSocket socket = mgr.connect(_server.getMyDestination());
|
||||
_log.debug("* socket created");
|
||||
InputStream in = socket.getInputStream();
|
||||
int c = in.read();
|
||||
if (c != -1)
|
||||
throw new RuntimeException("hrm, we got data? [" + c + "]");
|
||||
socket.close();
|
||||
_log.debug("* socket closed");
|
||||
mgr.destroySocketManager();
|
||||
mgr = null;
|
||||
socket = null;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private I2PSession createSession() {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
Destination dest = client.createDestination(baos);
|
||||
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), System.getProperties());
|
||||
sess.connect();
|
||||
return sess;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
throw new RuntimeException("b0rk b0rk b0rk");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
ConnectCloseTest ct = new ConnectCloseTest();
|
||||
ct.test();
|
||||
}
|
||||
}
|
@ -57,6 +57,7 @@ public class MessageOutputStreamTest {
|
||||
_data.write(buf, off, size);
|
||||
return new DummyWriteStatus();
|
||||
}
|
||||
public boolean writeInProcess() { return false; }
|
||||
public byte[] getData() { return _data.toByteArray(); }
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
<ant dir="apps/netmonitor/java/" target="jar" />
|
||||
<ant dir="apps/systray/java/" target="jar" />
|
||||
<ant dir="apps/routerconsole/java/" target="jar" />
|
||||
<ant dir="apps/addressbook/" target="war" />
|
||||
</target>
|
||||
<target name="buildWEB">
|
||||
<ant dir="apps/jetty" target="fetchJettylib" />
|
||||
@ -55,6 +56,7 @@
|
||||
<copy file="apps/netmonitor/java/build/netmonitor.jar" todir="build/" />
|
||||
<copy file="apps/systray/java/build/systray.jar" todir="build/" />
|
||||
<copy file="installer/lib/jbigi/jbigi.jar" todir="build" />
|
||||
<copy file="apps/addressbook/dist/addressbook.war" todir="build/" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
@ -102,6 +104,7 @@
|
||||
<ant dir="apps/heartbeat/java/" target="distclean" />
|
||||
<ant dir="apps/netmonitor/java/" target="distclean" />
|
||||
<ant dir="apps/routerconsole/java/" target="distclean" />
|
||||
<ant dir="apps/addressbook/" target="distclean" />
|
||||
<ant dir="apps/systray/java/" target="distclean" />
|
||||
<ant dir="installer/java/" target="distclean" />
|
||||
<delete>
|
||||
@ -178,6 +181,7 @@
|
||||
<copy file="build/xml-apis.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/routerconsole.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="installer/resources/clients.config" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/i2prouter" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/i2prouter.bat" todir="pkg-temp/" />
|
||||
@ -239,8 +243,7 @@
|
||||
<copy file="build/routerconsole.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/routerconsole.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="installer/resources/wrapper.config" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/wrapper.config" tofile="pkg-temp/wrapper.config.updated" />
|
||||
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="history.txt" todir="pkg-temp/" />
|
||||
<copy file="hosts.txt" todir="pkg-temp/" />
|
||||
<mkdir dir="pkg-temp/eepsite" />
|
||||
|
@ -1,5 +1,5 @@
|
||||
Prior to building the jbigi library, you will need to fetch the GMP source
|
||||
from http://www.swox.com/gmp/, saving it to jbigi/gmp-4.1.3.tar.bz2 (it will
|
||||
from http://www.swox.com/gmp/, saving it to jbigi/gmp-4.1.4.tar.bz2 (it will
|
||||
be unpacked and built as necessary).
|
||||
|
||||
To build the native jbigi and jcpuid libraries for the current host CPU,
|
||||
|
@ -7,7 +7,6 @@ mkdir -p t/freenet/support/CPUInformation/
|
||||
cp jcpuid/lib/freenet/support/CPUInformation/*jcpuid* t/freenet/support/CPUInformation/
|
||||
|
||||
mkdir -p t/net/i2p/util/
|
||||
cp jbigi/lib/net/i2p/util/*jbigi* t/net/i2p/util/
|
||||
cp jbigi/lib/*jbigi* t/
|
||||
|
||||
(cd t ; jar cf ../jbigi.jar . ; cd ..)
|
||||
|
@ -13,7 +13,7 @@ FreeBSD*)
|
||||
esac
|
||||
|
||||
echo "Extracting GMP..."
|
||||
tar -xjf gmp-4.1.3.tar.bz2
|
||||
tar -xjf gmp-4.1.4.tar.bz2
|
||||
echo "Building..."
|
||||
mkdir bin
|
||||
mkdir lib
|
||||
@ -24,7 +24,7 @@ for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon
|
||||
do
|
||||
mkdir bin/$x
|
||||
cd bin/$x
|
||||
../../gmp-4.1.3/configure --build=$x
|
||||
../../gmp-4.1.4/configure --build=$x
|
||||
make
|
||||
sh ../../build_jbigi.sh static
|
||||
case `uname -sr` in
|
||||
|
@ -1,41 +1,15 @@
|
||||
#/bin/sh
|
||||
|
||||
case `uname -sr` in
|
||||
MINGW*)
|
||||
echo "Building windows .dll's";;
|
||||
CYGWIN*)
|
||||
echo "Building windows .dll's";;
|
||||
Linux*)
|
||||
echo "Building linux .so's";;
|
||||
FreeBSD*)
|
||||
echo "Building freebsd .so's";;
|
||||
*)
|
||||
echo "Unsupported build environment"
|
||||
exit;;
|
||||
esac
|
||||
echo "Building the jbigi library with GMP"
|
||||
|
||||
echo "Extracting GMP..."
|
||||
tar -xjf gmp-4.1.3.tar.bz2
|
||||
tar -xjf gmp-4.1.4.tar.bz2
|
||||
echo "Building..."
|
||||
mkdir bin
|
||||
mkdir lib
|
||||
mkdir lib/net
|
||||
mkdir lib/net/i2p
|
||||
mkdir lib/net/i2p/util
|
||||
mkdir bin/local
|
||||
mkdir -p lib/
|
||||
mkdir -p bin/local
|
||||
cd bin/local
|
||||
../../gmp-4.1.3/configure
|
||||
../../gmp-4.1.4/configure
|
||||
make
|
||||
sh ../../build_jbigi.sh static
|
||||
case `uname -sr` in
|
||||
MINGW*)
|
||||
cp jbigi.dll ../../lib/jbigi;;
|
||||
CYGWIN*)
|
||||
cp jbigi.dll ../../lib/jbigi;;
|
||||
Linux*)
|
||||
cp libjbigi.so ../../lib/jbigi;;
|
||||
FreeBSD*)
|
||||
cp libjbigi.so ../../lib/jbigi;;
|
||||
esac
|
||||
cd ..
|
||||
cd ..
|
||||
cp *jbigi???* ../../lib/
|
||||
cd ../..
|
||||
|
@ -20,7 +20,7 @@ mkdir lib/freenet
|
||||
mkdir lib/freenet/support
|
||||
mkdir lib/freenet/support/CPUInformation
|
||||
|
||||
CPP="g++"
|
||||
CC="gcc"
|
||||
|
||||
case `uname -sr` in
|
||||
MINGW*)
|
||||
@ -43,7 +43,7 @@ esac
|
||||
|
||||
echo "Compiling C code..."
|
||||
rm -f $LIBFILE
|
||||
$CPP $LINKFLAGS $INCLUDES src/*.cpp -o $LIBFILE
|
||||
$CC $LINKFLAGS $INCLUDES src/*.c -o $LIBFILE
|
||||
strip $LIBFILE
|
||||
echo Built $LIBFILE
|
||||
|
||||
|
@ -5,8 +5,8 @@ JNIEXPORT jobject JNICALL Java_freenet_support_CPUInformation_CPUID_doCPUID
|
||||
(JNIEnv * env, jclass cls, jint iFunction)
|
||||
{
|
||||
int a,b,c,d;
|
||||
jclass clsResult = env->FindClass ("freenet/support/CPUInformation/CPUID$CPUIDResult");
|
||||
jmethodID constructor = env->GetMethodID(clsResult,"<init>","(IIII)V" );
|
||||
jclass clsResult = (*env)->FindClass(env, "freenet/support/CPUInformation/CPUID$CPUIDResult");
|
||||
jmethodID constructor = (*env)->GetMethodID(env, clsResult,"<init>","(IIII)V" );
|
||||
#ifdef _MSC_VER
|
||||
//Use MSVC assembler notation
|
||||
_asm
|
||||
@ -30,6 +30,6 @@ JNIEXPORT jobject JNICALL Java_freenet_support_CPUInformation_CPUID_doCPUID
|
||||
:"a"(iFunction)
|
||||
);
|
||||
#endif
|
||||
return env->NewObject(clsResult,constructor,a,b,c,d);
|
||||
return (*env)->NewObject(env, clsResult,constructor,a,b,c,d);
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ class I2CPMessageProducer {
|
||||
*
|
||||
*/
|
||||
public void disconnect(I2PSessionImpl session) throws I2PSessionException {
|
||||
if (session.isClosed()) return;
|
||||
DestroySessionMessage dmsg = new DestroySessionMessage();
|
||||
dmsg.setSessionId(session.getSessionId());
|
||||
session.sendMessage(dmsg);
|
||||
|
@ -105,9 +105,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
}
|
||||
return compressed;
|
||||
}
|
||||
|
||||
private static final int NUM_TAGS = 50;
|
||||
|
||||
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
long begin = _context.clock().now();
|
||||
|
||||
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
|
||||
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
@ -117,14 +121,14 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
|
||||
if ( (tagsSent == null) || (tagsSent.size() <= 0) ) {
|
||||
if (oldTags < 10) {
|
||||
sentTags = createNewTags(50);
|
||||
sentTags = createNewTags(NUM_TAGS);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("** sendBestEffort only had " + oldTags + " with " + availTimeLeft + ", adding 50");
|
||||
_log.debug("** sendBestEffort only had " + oldTags + " with " + availTimeLeft + ", adding " + NUM_TAGS);
|
||||
} else if (availTimeLeft < 2 * 60 * 1000) {
|
||||
// if we have > 10 tags, but they expire in under 2 minutes, we want more
|
||||
sentTags = createNewTags(50);
|
||||
sentTags = createNewTags(NUM_TAGS);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix() + "Tags expiring in " + availTimeLeft + ", adding 50 new ones");
|
||||
_log.debug(getPrefix() + "Tags expiring in " + availTimeLeft + ", adding " + NUM_TAGS + " new ones");
|
||||
//_log.error("** sendBestEffort available time left " + availTimeLeft);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -180,9 +184,17 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
+ " sync took " + (inSendingSync-beforeSendingSync)
|
||||
+ " add took " + (afterSendingSync-inSendingSync));
|
||||
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
|
||||
|
||||
// since this is 'best effort', all we're waiting for is a status update
|
||||
// saying that the router received it - in theory, that should come back
|
||||
// immediately, but in practice can take up to a second (though usually
|
||||
// much quicker). setting this to false will short-circuit that delay
|
||||
boolean actuallyWait = true;
|
||||
|
||||
long beforeWaitFor = _context.clock().now();
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
|
||||
_context.clock().now() + getTimeout());
|
||||
if (actuallyWait)
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
|
||||
_context.clock().now() + getTimeout());
|
||||
long afterWaitFor = _context.clock().now();
|
||||
long inRemovingSync = 0;
|
||||
synchronized (_sendingStates) {
|
||||
@ -190,7 +202,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
_sendingStates.remove(state);
|
||||
}
|
||||
long afterRemovingSync = _context.clock().now();
|
||||
boolean found = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
|
||||
boolean found = !actuallyWait || state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId()
|
||||
+ " / " + state.getNonce() + " found = " + found);
|
||||
@ -200,6 +212,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
_log.warn("wtf, took " + timeToSend + "ms to send the message?!", new Exception("baz"));
|
||||
}
|
||||
|
||||
if ( (afterRemovingSync - begin > 500) && (_log.shouldLog(Log.WARN) ) ) {
|
||||
_log.warn("Took " + (afterRemovingSync-begin) + "ms to sendBestEffort, "
|
||||
+ (afterSendingSync-begin) + "ms to prepare, "
|
||||
+ (beforeWaitFor-afterSendingSync) + "ms to send, "
|
||||
+ (afterRemovingSync-beforeWaitFor) + "ms waiting for reply");
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + "Message sent after " + state.getElapsed() + "ms with "
|
||||
@ -223,11 +242,11 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
Set sentTags = null;
|
||||
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
|
||||
sentTags = createNewTags(50);
|
||||
sentTags = createNewTags(NUM_TAGS);
|
||||
} else if (_context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key) < 2 * 60 * 1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
sentTags = createNewTags(50);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones");
|
||||
sentTags = createNewTags(NUM_TAGS);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding " + NUM_TAGS + " new ones");
|
||||
}
|
||||
SessionKey newKey = null;
|
||||
if (false) // rekey
|
||||
|
@ -16,6 +16,7 @@ import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handle I2CP MessagePayloadMessages from the router delivering the contents
|
||||
@ -30,7 +31,8 @@ class MessagePayloadMessageHandler extends HandlerImpl {
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
_log.debug("Handle message " + message);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handle message " + message);
|
||||
try {
|
||||
MessagePayloadMessage msg = (MessagePayloadMessage) message;
|
||||
MessageId id = msg.getMessageId();
|
||||
@ -55,9 +57,8 @@ class MessagePayloadMessageHandler extends HandlerImpl {
|
||||
Payload payload = msg.getPayload();
|
||||
byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey());
|
||||
if (data == null) {
|
||||
_log
|
||||
.error("Error decrypting the payload to public key "
|
||||
+ session.getMyDestination().getPublicKey().toBase64() + "\nPayload: " + payload.calculateHash());
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error decrypting the payload");
|
||||
throw new DataFormatException("Unable to decrypt the payload");
|
||||
}
|
||||
payload.setUnencryptedData(data);
|
||||
|
@ -22,7 +22,8 @@ public abstract class NamingService {
|
||||
private final static Log _log = new Log(NamingService.class);
|
||||
protected I2PAppContext _context;
|
||||
|
||||
private static final String PROP_IMPL = "i2p.naming.impl";
|
||||
/** what classname should be used as the naming service impl? */
|
||||
public static final String PROP_IMPL = "i2p.naming.impl";
|
||||
private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
|
||||
|
||||
|
||||
|
@ -27,7 +27,7 @@ but there are three other subpackages that are helpful. Specifically:<ul>
|
||||
<li>{@link net.i2p.client.streaming} - for applications that want to use
|
||||
a streaming API to provide reliable in order message delivery (<b>note</b>:
|
||||
the streaming library is packaged seperate from the main SDK - in the
|
||||
ministreaming.jar)</li>
|
||||
mstreaming.jar and streaming.jar)</li>
|
||||
</ul></p>
|
||||
|
||||
<p>The {@link net.i2p.client.I2PSession} implementation itself communicates with
|
||||
|
@ -127,6 +127,21 @@ public class AESEngine {
|
||||
_log.warn("Warning: AES is disabled");
|
||||
}
|
||||
|
||||
|
||||
public void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
|
||||
System.arraycopy(payload, inIndex, out, outIndex, out.length - outIndex);
|
||||
}
|
||||
|
||||
/** decrypt the data with the session key provided
|
||||
* @param payload encrypted data
|
||||
* @param sessionKey private session key
|
||||
* @return unencrypted data
|
||||
*/
|
||||
public void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
|
||||
System.arraycopy(payload, inIndex, rv, outIndex, rv.length - outIndex);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
|
@ -161,7 +161,7 @@ public class AESInputStream extends FilterInputStream {
|
||||
*
|
||||
*/
|
||||
private void refill() throws IOException {
|
||||
if (!_eofFound) {
|
||||
if ( (!_eofFound) && (_writesSinceDecrypt < BLOCK_SIZE) ) {
|
||||
int read = in.read(_encryptedBuf, _writesSinceDecrypt, _encryptedBuf.length - _writesSinceDecrypt);
|
||||
if (read == -1) {
|
||||
_eofFound = true;
|
||||
|
@ -85,14 +85,14 @@ public class CryptixAESEngine extends AESEngine {
|
||||
if (length % 16 != 0) numblock++;
|
||||
|
||||
decryptBlock(payload, payloadIndex, sessionKey, out, outIndex);
|
||||
DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16);
|
||||
DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16);
|
||||
for (int x = 1; x < numblock; x++) {
|
||||
decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
|
||||
DataHelper.xor(out, outIndex + x * 16, payload, payloadIndex + (x - 1) * 16, out, outIndex + x * 16, 16);
|
||||
}
|
||||
}
|
||||
|
||||
final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
|
||||
public final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
|
||||
CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey();
|
||||
try {
|
||||
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData);
|
||||
@ -109,7 +109,7 @@ public class CryptixAESEngine extends AESEngine {
|
||||
* @param sessionKey private session key
|
||||
* @return unencrypted data
|
||||
*/
|
||||
final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
|
||||
public final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
|
||||
if ( (payload == null) || (rv == null) )
|
||||
throw new IllegalArgumentException("null block args [payload=" + payload + " rv="+rv);
|
||||
if (payload.length - inIndex > rv.length - outIndex)
|
||||
@ -131,9 +131,11 @@ public class CryptixAESEngine extends AESEngine {
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
try {
|
||||
testEDBlock(ctx);
|
||||
testEDBlock2(ctx);
|
||||
testED(ctx);
|
||||
testFake(ctx);
|
||||
testNull(ctx);
|
||||
testED2(ctx);
|
||||
//testFake(ctx);
|
||||
//testNull(ctx);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -155,6 +157,21 @@ public class CryptixAESEngine extends AESEngine {
|
||||
else
|
||||
System.out.println("full D(E(orig)) == orig");
|
||||
}
|
||||
private static void testED2(I2PAppContext ctx) {
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
byte iv[] = new byte[16];
|
||||
byte orig[] = new byte[128];
|
||||
byte data[] = new byte[128];
|
||||
ctx.random().nextBytes(iv);
|
||||
ctx.random().nextBytes(orig);
|
||||
CryptixAESEngine aes = new CryptixAESEngine(ctx);
|
||||
aes.encrypt(orig, 0, data, 0, key, iv, data.length);
|
||||
aes.decrypt(data, 0, data, 0, key, iv, data.length);
|
||||
if (!DataHelper.eq(data,orig))
|
||||
throw new RuntimeException("full D(E(orig)) != orig");
|
||||
else
|
||||
System.out.println("full D(E(orig)) == orig");
|
||||
}
|
||||
private static void testFake(I2PAppContext ctx) {
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
SessionKey wrongKey = ctx.keyGenerator().generateSessionKey();
|
||||
@ -207,4 +224,19 @@ public class CryptixAESEngine extends AESEngine {
|
||||
else
|
||||
System.out.println("block D(E(orig)) == orig");
|
||||
}
|
||||
private static void testEDBlock2(I2PAppContext ctx) {
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
byte iv[] = new byte[16];
|
||||
byte orig[] = new byte[16];
|
||||
byte data[] = new byte[16];
|
||||
ctx.random().nextBytes(iv);
|
||||
ctx.random().nextBytes(orig);
|
||||
CryptixAESEngine aes = new CryptixAESEngine(ctx);
|
||||
aes.encryptBlock(orig, 0, key, data, 0);
|
||||
aes.decryptBlock(data, 0, key, data, 0);
|
||||
if (!DataHelper.eq(data,orig))
|
||||
throw new RuntimeException("block D(E(orig)) != orig");
|
||||
else
|
||||
System.out.println("block D(E(orig)) == orig");
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user