forked from I2P_Developers/i2p.i2p
Compare commits
391 Commits
i2p-0.9.19
...
i2p-0.9.22
| Author | SHA1 | Date | |
|---|---|---|---|
| fd606064d9 | |||
| 9d05424202 | |||
|
|
157d494dee | ||
|
|
fa792a9d5e | ||
|
|
ab134261f0 | ||
|
|
de2431e9ee | ||
|
|
c4cbd7d5c4 | ||
| e978bb81a0 | |||
| 2c6edf401f | |||
| fe69d3b8f7 | |||
|
|
61edd01e3d | ||
| 483d7c43ee | |||
| 7c703953be | |||
| f577a94012 | |||
| b10b8581cc | |||
| 601376561b | |||
| 5a11a28a35 | |||
|
|
fde0ae8349 | ||
|
|
b5944045fb | ||
| ecd0231cd0 | |||
| 44b35f328b | |||
| f3bb20d750 | |||
|
|
20cb284f9d | ||
|
|
b4993d42b3 | ||
|
|
9b466f3261 | ||
| 0bf9cb3bf2 | |||
| 9efe60d7a8 | |||
| d848a19ab0 | |||
| bfde521cf9 | |||
| fea6b8aec3 | |||
|
|
1681598dec | ||
|
|
809a533573 | ||
|
|
265e4b58a5 | ||
|
|
93854e93b5 | ||
|
|
f6605d05d9 | ||
|
|
c20772702a | ||
|
|
ba5af15c6f | ||
|
|
9af197e590 | ||
|
|
2f59a4b3e6 | ||
|
|
63e934f8f2 | ||
| dd5f804150 | |||
|
|
35b0e99ff0 | ||
| 1ed1e4414b | |||
| d087fd674b | |||
| 1f9bb046f5 | |||
| 914cc120ad | |||
|
|
631a0674ab | ||
|
|
17d26976d5 | ||
|
|
dc9d60e261 | ||
| 2c191e7bf8 | |||
| 817888c23c | |||
| 1eaf376ee7 | |||
| 6cb3d1d330 | |||
| 2681c4b42f | |||
| 05959d5199 | |||
| 113a8a52f3 | |||
| 98a4460bde | |||
|
|
3645c906e8 | ||
| fcdd8be7a7 | |||
| 34f6f65104 | |||
| 4c516cd2af | |||
|
|
8ea6805f8d | ||
| 23f2261bd9 | |||
| dd47389ad1 | |||
| 25268e7cb2 | |||
| 355b2a1528 | |||
| 975149d049 | |||
| af394e13ad | |||
| e3f64f6edf | |||
|
|
2fbbfa388e | ||
| 0b4d4ddcbc | |||
| 428d89a307 | |||
|
|
feff6c003b | ||
|
|
699d550992 | ||
|
|
c6896c4418 | ||
| 1b2d4c75eb | |||
| 586defc802 | |||
| 2499aad51d | |||
| addb142ecd | |||
| 20c796e87a | |||
| cd62d7170c | |||
|
|
acc647822f | ||
| 1cf544f1d4 | |||
| 0f4e09500c | |||
| 7c5dfaee20 | |||
| 8d9cced128 | |||
| 8096e4f65d | |||
| 036b77746b | |||
| bc85543ef2 | |||
|
|
627f7076b0 | ||
|
|
863e120204 | ||
|
|
53cfba4cbd | ||
|
|
3a774b7c37 | ||
| 0ad34a4b00 | |||
| 2b9ffc1270 | |||
| 93c7860d2b | |||
|
|
25f6c3d9e1 | ||
| b9e07bc9aa | |||
| 09f68e44ca | |||
| 013b5fd85b | |||
| 8962bfb6bc | |||
| 605602e001 | |||
| f341e5566b | |||
| 7b84676f4a | |||
|
|
c666f8a4f9 | ||
|
|
e067761947 | ||
|
|
226bee64ef | ||
|
|
1a40e57413 | ||
|
|
f73101b014 | ||
| fef65c996f | |||
| cbc2f899a6 | |||
| 099515adff | |||
|
|
ff2ea9ac3e | ||
|
|
97aeecd865 | ||
|
|
8098d705f9 | ||
|
|
fa8c390267 | ||
| e8f4e19bac | |||
| 9041a2c69f | |||
| 384e9118c6 | |||
|
|
0936a2ee23 | ||
|
|
bc6b0c12ac | ||
|
|
f6f051cfa4 | ||
| fb131a040c | |||
| 9f2ded6073 | |||
| 55e36ee458 | |||
|
|
7c13fb2ba0 | ||
|
|
663ccb72d7 | ||
|
|
78e0a37fc9 | ||
|
|
09cdc00939 | ||
|
|
2590e7d4ff | ||
| 27f56776ca | |||
| 657f13af29 | |||
| e2ca74963f | |||
| 9304cb2bbc | |||
| 362086994a | |||
| f57e37d588 | |||
| d96ddd1a0e | |||
| 7b711ebba0 | |||
| 0762715264 | |||
| 8a69dc0a97 | |||
| 39dc60cf8a | |||
| 09e867b194 | |||
| dc9256f274 | |||
| 272f63dbbd | |||
| 06104118d0 | |||
| 525ec01c1e | |||
| f8594c316f | |||
| 3c89bd4e19 | |||
| 1f8408f417 | |||
| 915b35f0c1 | |||
| 4521156ecb | |||
| c58fd8f84e | |||
| f02b401b7a | |||
| 4fdcb6ce29 | |||
| 94824e4d2b | |||
| 280fc05c91 | |||
| 89745f5002 | |||
| 7715e6484c | |||
| c807194e93 | |||
| 3602f73497 | |||
| 4bf115b5f6 | |||
| 7ab85a0a20 | |||
| fba0372339 | |||
| 03dfa6515b | |||
| 5e33ed1169 | |||
| 11ab7fc56c | |||
| 716bff41d7 | |||
| 1d8842cfc8 | |||
| 042b03d6b8 | |||
|
|
ab753651b9 | ||
| 4ea99b8a10 | |||
| 3d07e1a10b | |||
| 86525e7239 | |||
| 195171f9ed | |||
|
|
33c4be5b2f | ||
|
|
fea2c3c6b2 | ||
|
|
281686ba58 | ||
|
|
0b91fcb636 | ||
|
|
807f1381fb | ||
|
|
ac56a63809 | ||
|
|
e4798b9ed8 | ||
|
|
7584346c82 | ||
| 29330aa5d3 | |||
| 65ff2c0afe | |||
| de4d47de95 | |||
| ae41a3f316 | |||
| 2dc3d68418 | |||
| 5eb43b6ae4 | |||
| 0c672ecc49 | |||
|
|
3b6d98fe38 | ||
|
|
b3472cfe80 | ||
|
|
38f2b93c7a | ||
| e7af87a981 | |||
| d698a67660 | |||
| b38f2d62a8 | |||
| 10556bca75 | |||
| 1f17d2a149 | |||
|
|
dc777c8de5 | ||
| 1fb9643916 | |||
| 081f1865a8 | |||
| 0e17c560b3 | |||
| a3b1327934 | |||
| e68ca573f0 | |||
| b5455cee6e | |||
|
|
cbdc1403bf | ||
| 40130a8a61 | |||
|
|
ca14055976 | ||
|
|
8303016b48 | ||
|
|
287862887d | ||
| f25d2a3d3f | |||
| 7f30f481b2 | |||
| 5ee6826241 | |||
| 68951c4c6b | |||
| 5dc7497802 | |||
| 31cfddc218 | |||
| c1e70ac7d2 | |||
| dd9abd3f09 | |||
| 2f5e64e532 | |||
| b12f988390 | |||
|
|
9f3d5bf57b | ||
| 7f9e958e5a | |||
| c4877ea092 | |||
| 2aafc23774 | |||
| 77c9a644ac | |||
| abd8ca34dc | |||
|
|
31435685bf | ||
| 7337fd0670 | |||
| f7b7a98b9d | |||
|
|
2226936737 | ||
|
|
8b293b2190 | ||
| 94bba8d11f | |||
|
|
5c2b5075f9 | ||
|
|
ca6820a4c0 | ||
| 2fafa3337f | |||
| b5f75a4bb9 | |||
| 707bfbbf8b | |||
| 1eba6c5167 | |||
| a14208b841 | |||
|
|
83966f9a7f | ||
| d89f06015b | |||
| 49f786c928 | |||
| e8bc0bd5d1 | |||
|
|
8d69b69357 | ||
|
|
e7b9a230e6 | ||
|
|
6385c412fd | ||
| bb33b358b4 | |||
| 572f071cfe | |||
| 42cb89f525 | |||
| 1868d2b50f | |||
| 4588f1ec75 | |||
| 629f7f05c7 | |||
| 0f18686243 | |||
| 2a2587b13d | |||
| 489fdd5e4b | |||
| fe680eb192 | |||
| 613440ff63 | |||
| 64121b1e92 | |||
| f16927f316 | |||
| cb50c1bd8b | |||
| 921ad86274 | |||
| ac76107752 | |||
| 2359b1edd2 | |||
| 2750681d78 | |||
| eaac4d3de0 | |||
| f243968dfa | |||
| 8d9e2bdc71 | |||
| 6dbbb6b61b | |||
| f89bf32390 | |||
| ef195aa4ef | |||
|
|
843230a1cb | ||
| 84e63f3b38 | |||
| b90816fdf8 | |||
| 40c4a42921 | |||
| 26f89391d3 | |||
| aaae72cf84 | |||
| 3e55cff153 | |||
| bd778a2204 | |||
| 235c196f14 | |||
| e475c161cb | |||
| 08e96109a7 | |||
| 81ad33d9e3 | |||
| aecc95825b | |||
| 37c6ac3a88 | |||
| 772d0beac3 | |||
| 64fdfd81ee | |||
| 1b09b9faa4 | |||
| 6f0ebb2d94 | |||
| cbe91e3012 | |||
| 238501919b | |||
| ae3a5f7b25 | |||
| 638cadc3c9 | |||
| da0036581c | |||
| 59a58ea310 | |||
| bebe5f8a4e | |||
| c3af99685d | |||
| e1d9e05b8d | |||
| 212f6b472a | |||
| fdada78edf | |||
| 638c5429d2 | |||
| b67bbd7065 | |||
| 1caf3e778b | |||
| fd82fff07a | |||
| a6ac8f8c09 | |||
| 19a26f8e22 | |||
| 46e85cf265 | |||
| 8f321b5427 | |||
| e1f8f1a3f4 | |||
| 935a5b573d | |||
| 8c2636aa99 | |||
| 03ddb1075c | |||
| 72eb2c058c | |||
| a100d2ccf9 | |||
| ecfb3e94c8 | |||
| c31d6b1ac1 | |||
| 65993e1d50 | |||
| 47c4c0d6bb | |||
| b2872e6110 | |||
| b8c8d5b447 | |||
| 32049d7bfc | |||
| f0fdb35ba6 | |||
| d8baf62966 | |||
| be8f7f9676 | |||
| 57b641bf63 | |||
| ff5d29de1a | |||
| 91e98ba447 | |||
| 6a644dd0e5 | |||
| 7b82393336 | |||
|
|
22993e1ea6 | ||
|
|
341bd6d7ca | ||
|
|
13d5a36cfc | ||
|
|
f3bb84f2c0 | ||
|
|
1d496404be | ||
| 51233371e0 | |||
| bc0a7ebbbc | |||
| 72c78b3870 | |||
| 5555c52376 | |||
|
|
e1842be049 | ||
| 6ceb4fcf42 | |||
| 50b68d4e1c | |||
| 3f46228f0b | |||
| 6c954f0b68 | |||
| 69c2ed77a0 | |||
| 6f09224bdc | |||
| 568c90806d | |||
| 6e451c8d4d | |||
| 12fd585625 | |||
| 997fbb3392 | |||
| 089626f6b1 | |||
| 71d2049fe8 | |||
| e5aee3001f | |||
|
|
ec62bcbf8e | ||
|
|
a8f013f3e4 | ||
|
|
037cd78dc7 | ||
|
|
b31ae4bae5 | ||
|
|
54dba980b4 | ||
|
|
dc19d2fab3 | ||
|
|
3a57310fbe | ||
|
|
749e19a1c3 | ||
| de6608f6b8 | |||
| cd6d9cdd94 | |||
| e45413d417 | |||
|
|
11c3230150 | ||
|
|
f5ba1b1b97 | ||
| 7825f0f84f | |||
| e5b7e97ff4 | |||
|
|
4613e5f847 | ||
|
|
44f8154f07 | ||
| 5486874d1a | |||
| d868ca4740 | |||
| 780479be4b | |||
| 4705f01bc5 | |||
| 2f5f91a084 | |||
|
|
e44fe98c7e | ||
|
|
d8fbc9c170 | ||
|
|
facbe8f9a0 | ||
|
|
4d8e577ffd | ||
|
|
80eb7635c1 | ||
|
|
e3103762b6 | ||
| 6aa1284848 | |||
| bb082c35fc | |||
| f7577e7de8 | |||
| b5df13d8b7 | |||
| 75a8d8f6d3 | |||
| 1ac8d99145 | |||
| 485acd6c8d | |||
| 3ccb03f9be | |||
| f3a2af8f10 | |||
| 9dc2ae0d7e | |||
| 188bd6db7b | |||
| 3a8ce64c84 | |||
| 1293dccf35 |
20
.tx/config
20
.tx/config
@@ -33,7 +33,8 @@ trans.de = apps/i2ptunnel/locale-proxy/messages_de.po
|
||||
trans.es = apps/i2ptunnel/locale-proxy/messages_es.po
|
||||
trans.fr = apps/i2ptunnel/locale-proxy/messages_fr.po
|
||||
trans.hu = apps/i2ptunnel/locale-proxy/messages_hu.po
|
||||
trans.id = apps/i2ptunnel/locale-proxy/messages_id.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/i2ptunnel/locale-proxy/messages_in.po
|
||||
trans.it = apps/i2ptunnel/locale-proxy/messages_it.po
|
||||
trans.nb = apps/i2ptunnel/locale-proxy/messages_nb.po
|
||||
trans.nl = apps/i2ptunnel/locale-proxy/messages_nl.po
|
||||
@@ -85,7 +86,8 @@ trans.es = apps/routerconsole/locale-news/messages_es.po
|
||||
trans.fi = apps/routerconsole/locale-news/messages_fi.po
|
||||
trans.fr = apps/routerconsole/locale-news/messages_fr.po
|
||||
trans.he = apps/routerconsole/locale-news/messages_he.po
|
||||
trans.id = apps/routerconsole/locale-news/messages_id.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/routerconsole/locale-news/messages_in.po
|
||||
trans.it = apps/routerconsole/locale-news/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-news/messages_ja.po
|
||||
trans.ko = apps/routerconsole/locale-news/messages_ko.po
|
||||
@@ -217,7 +219,8 @@ trans.es = apps/susimail/locale/messages_es.po
|
||||
trans.fi = apps/susimail/locale/messages_fi.po
|
||||
trans.fr = apps/susimail/locale/messages_fr.po
|
||||
trans.hu = apps/susimail/locale/messages_hu.po
|
||||
trans.id = apps/susimail/locale/messages_id.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/susimail/locale/messages_in.po
|
||||
trans.it = apps/susimail/locale/messages_it.po
|
||||
trans.ja = apps/susimail/locale/messages_ja.po
|
||||
trans.mg = apps/susimail/locale/messages_mg.po
|
||||
@@ -267,6 +270,8 @@ source_lang = en
|
||||
;;trans.ca = installer/resources/locale/po/messages_ca.po
|
||||
trans.de = installer/resources/locale/po/messages_de.po
|
||||
trans.es = installer/resources/locale/po/messages_es.po
|
||||
;; currently fails check
|
||||
;;trans.fi = installer/resources/locale/po/messages_fi.po
|
||||
trans.fr = installer/resources/locale/po/messages_fr.po
|
||||
trans.id = installer/resources/locale/po/messages_id.po
|
||||
trans.it = installer/resources/locale/po/messages_it.po
|
||||
@@ -294,9 +299,11 @@ type = PROPERTIES
|
||||
trans.cs = core/java/src/gnu/getopt/MessagesBundle_cs.properties
|
||||
trans.de = core/java/src/gnu/getopt/MessagesBundle_de.properties
|
||||
trans.es = core/java/src/gnu/getopt/MessagesBundle_es.properties
|
||||
trans.fi = core/java/src/gnu/getopt/MessagesBundle_fi.properties
|
||||
trans.fr = core/java/src/gnu/getopt/MessagesBundle_fr.properties
|
||||
trans.hu = core/java/src/gnu/getopt/MessagesBundle_hu.properties
|
||||
trans.id = core/java/src/gnu/getopt/MessagesBundle_id.properties
|
||||
;; Java converts id to in
|
||||
trans.id = core/java/src/gnu/getopt/MessagesBundle_in.properties
|
||||
trans.it = core/java/src/gnu/getopt/MessagesBundle_it.properties
|
||||
trans.ja = core/java/src/gnu/getopt/MessagesBundle_ja.properties
|
||||
trans.ko = core/java/src/gnu/getopt/MessagesBundle_ko.properties
|
||||
@@ -318,15 +325,18 @@ trans.zh_CN = core/java/src/gnu/getopt/MessagesBundle_zh.properties
|
||||
[I2P.streaming]
|
||||
source_file = apps/ministreaming/locale/messages_en.po
|
||||
source_lang = en
|
||||
trans.ca = apps/ministreaming/locale/messages_ca.po
|
||||
trans.de = apps/ministreaming/locale/messages_de.po
|
||||
trans.es = apps/ministreaming/locale/messages_es.po
|
||||
trans.fr = apps/ministreaming/locale/messages_fr.po
|
||||
trans.id = apps/ministreaming/locale/messages_id.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/ministreaming/locale/messages_in.po
|
||||
trans.it = apps/ministreaming/locale/messages_it.po
|
||||
trans.nb = apps/ministreaming/locale/messages_nb.po
|
||||
trans.pl = apps/ministreaming/locale/messages_pl.po
|
||||
trans.ro = apps/ministreaming/locale/messages_ro.po
|
||||
trans.ru_RU = apps/ministreaming/locale/messages_ru.po
|
||||
trans.sv_SE = apps/ministreaming/locale/messages_sv.po
|
||||
trans.uk_UA = apps/ministreaming/locale/messages_uk.po
|
||||
trans.zh_CN = apps/ministreaming/locale/messages_zh.po
|
||||
|
||||
|
||||
12
LICENSE.txt
12
LICENSE.txt
@@ -80,6 +80,10 @@ Public domain except as listed below:
|
||||
Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
HostnameVerifier:
|
||||
From Apache HttpClient 4.4.1 and HttpCore 4.4.1
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
|
||||
Router (router.jar):
|
||||
Public domain except as listed below:
|
||||
@@ -87,7 +91,7 @@ Public domain except as listed below:
|
||||
From freenet
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
UPnP subsystem (CyberLink) 2.1:
|
||||
UPnP subsystem (CyberLink) 3.0:
|
||||
Copyright (C) 2003-2010 Satoshi Konno
|
||||
See licenses/LICENSE-UPnP.txt
|
||||
|
||||
@@ -182,7 +186,7 @@ Applications:
|
||||
By welterde.
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Jetty 8.1.16.v20140903:
|
||||
Jetty 8.1.17.v20150415:
|
||||
See licenses/ABOUT-Jetty.html
|
||||
See licenses/NOTICE-Jetty.html
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
@@ -248,8 +252,8 @@ Applications:
|
||||
Bundles systray4j-2.4.1:
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
Tomcat 6.0.43:
|
||||
Copyright 1999-2014 The Apache Software Foundation
|
||||
Tomcat 6.0.44:
|
||||
Copyright 1999-2015 The Apache Software Foundation
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/NOTICE-Tomcat.txt
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.*;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
@@ -214,9 +213,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
// Re-reading the config file in each thread is pretty damn stupid.
|
||||
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
|
||||
// This is here just to ensure there is no interference with our threadgroups.
|
||||
SimpleScheduler Y1 = SimpleScheduler.getInstance();
|
||||
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
|
||||
i = Y1.hashCode();
|
||||
i = Y2.hashCode();
|
||||
{
|
||||
File cfg = new File(configLocation);
|
||||
|
||||
@@ -30,16 +30,15 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
*/
|
||||
public class I2Plistener implements Runnable {
|
||||
|
||||
private NamedDB info, database;
|
||||
private Logger _log;
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
private AtomicBoolean lives;
|
||||
private final NamedDB info, database;
|
||||
private final Logger _log;
|
||||
private final I2PServerSocket serverSocket;
|
||||
private final AtomicBoolean lives;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param SS
|
||||
* @param S
|
||||
* @param S unused
|
||||
* @param info
|
||||
* @param database
|
||||
* @param _log
|
||||
@@ -48,7 +47,6 @@ public class I2Plistener implements Runnable {
|
||||
this.database = database;
|
||||
this.info = info;
|
||||
this._log = _log;
|
||||
this.socketManager = S;
|
||||
this.serverSocket = SS;
|
||||
this.lives = lives;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package net.i2p.BOB;
|
||||
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
@@ -31,12 +30,10 @@ public class Main {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// THINK THINK THINK THINK THINK THINK
|
||||
SimpleScheduler Y1 = SimpleScheduler.getInstance();
|
||||
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
|
||||
|
||||
BOB.main(args);
|
||||
|
||||
Y2.stop();
|
||||
Y1.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class NamedDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find objects in the array, returns it's index or throws exception
|
||||
* Find objects in the array, returns its index or throws exception
|
||||
* @param key
|
||||
* @return an objects index
|
||||
* @throws ArrayIndexOutOfBoundsException when key does not exist
|
||||
|
||||
@@ -30,12 +30,11 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
*/
|
||||
public class TCPlistener implements Runnable {
|
||||
|
||||
private NamedDB info, database;
|
||||
private Logger _log;
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
private ServerSocket listener;
|
||||
private AtomicBoolean lives;
|
||||
private final NamedDB info, database;
|
||||
private final Logger _log;
|
||||
private final I2PSocketManager socketManager;
|
||||
private final ServerSocket listener;
|
||||
private final AtomicBoolean lives;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -34,15 +34,17 @@ import net.i2p.util.Log;
|
||||
* The skeletal frame is here, just needs to be finished.
|
||||
*
|
||||
* @author sponge
|
||||
* @deprecated incomplete, unused
|
||||
*/
|
||||
public class UDPIOthread implements I2PSessionListener, Runnable {
|
||||
|
||||
private NamedDB info;
|
||||
private Log _log;
|
||||
private Socket socket;
|
||||
private final NamedDB info;
|
||||
private final Log _log;
|
||||
private final Socket socket;
|
||||
private DataInputStream in;
|
||||
private DataOutputStream out;
|
||||
private I2PSession _session;
|
||||
private final I2PSession _session;
|
||||
// FIXME never set
|
||||
private Destination _peerDestination;
|
||||
private boolean up;
|
||||
|
||||
@@ -58,7 +60,6 @@ public class UDPIOthread implements I2PSessionListener, Runnable {
|
||||
this._log = _log;
|
||||
this.socket = socket;
|
||||
this._session = _session;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -159,8 +159,13 @@ class AddressBook {
|
||||
* @since 0.8.7
|
||||
*/
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
if (this.subFile != null)
|
||||
return new ConfigIterator(this.subFile);
|
||||
if (this.subFile != null) {
|
||||
try {
|
||||
return new ConfigIterator(this.subFile);
|
||||
} catch (IOException ioe) {
|
||||
return new ConfigIterator();
|
||||
}
|
||||
}
|
||||
return this.addresses.entrySet().iterator();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -41,7 +42,7 @@ import java.util.NoSuchElementException;
|
||||
*
|
||||
* @since 0.8.7
|
||||
*/
|
||||
class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
class ConfigIterator implements Iterator<Map.Entry<String, String>>, Closeable {
|
||||
|
||||
private BufferedReader input;
|
||||
private ConfigEntry next;
|
||||
@@ -54,11 +55,9 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
/**
|
||||
* An iterator over the key/value pairs in the file.
|
||||
*/
|
||||
public ConfigIterator(File file) {
|
||||
try {
|
||||
public ConfigIterator(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
input = new BufferedReader(new InputStreamReader(fileStream));
|
||||
} catch (IOException ioe) {}
|
||||
input = new BufferedReader(new InputStreamReader(fileStream, "UTF-8"));
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
|
||||
@@ -116,7 +116,7 @@ class ConfigParser {
|
||||
public static Map<String, String> parse(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
fileStream, "UTF-8"));
|
||||
Map<String, String> rv = parse(input);
|
||||
try {
|
||||
fileStream.close();
|
||||
@@ -205,7 +205,7 @@ class ConfigParser {
|
||||
public static List<String> parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
fileStream, "UTF-8"));
|
||||
List<String> rv = parseSubscriptions(input);
|
||||
try {
|
||||
fileStream.close();
|
||||
|
||||
@@ -23,8 +23,9 @@ package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -56,8 +57,8 @@ class Log {
|
||||
public void append(String entry) {
|
||||
BufferedWriter bw = null;
|
||||
try {
|
||||
bw = new BufferedWriter(new FileWriter(this.file,
|
||||
true));
|
||||
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.file,
|
||||
true), "UTF-8"));
|
||||
String timestamp = new Date().toString();
|
||||
bw.write(timestamp + " -- " + entry);
|
||||
bw.newLine();
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
@@ -69,11 +70,13 @@ class SubscriptionIterator implements Iterator<AddressBook> {
|
||||
* Yes, the EepGet fetch() is done in here in next().
|
||||
*
|
||||
* see java.util.Iterator#next()
|
||||
* @return an AddressBook (empty if the minimum delay has not been met)
|
||||
* @return non-null AddressBook (empty if the minimum delay has not been met,
|
||||
* or there is no proxy tunnel, or the fetch otherwise fails)
|
||||
*/
|
||||
public AddressBook next() {
|
||||
Subscription sub = this.subIterator.next();
|
||||
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now()) {
|
||||
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now() &&
|
||||
I2PAppContext.getGlobalContext().portMapper().getPort(PortMapper.SVC_HTTP_PROXY) >= 0) {
|
||||
//System.err.println("Fetching addressbook from " + sub.getLocation());
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
} else {
|
||||
|
||||
@@ -28,4 +28,11 @@
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- this webapp doesn't actually use sessions or cookies -->
|
||||
<session-config>
|
||||
<session-timeout>30</session-timeout>
|
||||
<cookie-config>
|
||||
<http-only>true</http-only>
|
||||
</cookie-config>
|
||||
</session-config>
|
||||
</web-app>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Last Modified: Mon, 16 Feb 2015
|
||||
# Last Modified: Sun Apr 12 22:08:32 2015
|
||||
# vim:syntax=apparmor et ts=8 sw=4
|
||||
|
||||
#include <tunables/global>
|
||||
@@ -18,20 +18,20 @@ $INSTALL_PATH/{i2prouter,runplain.sh} flags=(complain) {
|
||||
owner $INSTALL_PATH/** rwklm,
|
||||
|
||||
# Needed for Java
|
||||
@{PROC} r,
|
||||
@{PROC}/[0-9]*/net/if_inet6 r,
|
||||
@{PROC}/[0-9]*/net/ipv6_route r,
|
||||
@{PROC}/[0-9]*/status r,
|
||||
@{PROC}/[0-9]*/stat r,
|
||||
@{PROC}/[0-9]*/cmdline r,
|
||||
@{PROC}/1/comm r,
|
||||
owner @{PROC} r,
|
||||
owner @{PROC}/[0-9]*/ r,
|
||||
owner @{PROC}/[0-9]*/status r,
|
||||
owner @{PROC}/[0-9]*/stat r,
|
||||
owner @{PROC}/[0-9]*/cmdline r,
|
||||
@{PROC}/uptime r,
|
||||
@{PROC}/sys/kernel/pid_max r,
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/cpu/** r,
|
||||
|
||||
/dev/random r,
|
||||
/dev/urandom r,
|
||||
|
||||
@{PROC}/1/comm r,
|
||||
|
||||
/etc/ssl/certs/java/** r,
|
||||
/etc/timezone r,
|
||||
@@ -51,16 +51,7 @@ $INSTALL_PATH/{i2prouter,runplain.sh} flags=(complain) {
|
||||
|
||||
|
||||
# Fonts are needed for I2P's graphs
|
||||
/etc/fonts/** r,
|
||||
/usr/share/fontconfig/ r,
|
||||
/usr/share/fontconfig/** r,
|
||||
/usr/share/fonts/ r,
|
||||
/usr/share/fonts/** r,
|
||||
/usr/share/fonts/truetype/ r,
|
||||
/usr/share/fonts/truetype/** r,
|
||||
/usr/share/java/java-atk-wrapper.jar r,
|
||||
/var/cache/fontconfig/ r,
|
||||
/var/cache/fontconfig/** r,
|
||||
|
||||
# Used by some plugins
|
||||
/usr/share/java/eclipse-ecj-*.jar r,
|
||||
|
||||
@@ -26,7 +26,7 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Denis Blank <gribua@gmail.com>, 2011
|
||||
# Denis Lysenko <gribua@gmail.com>, 2011
|
||||
# LinuxChata, 2014
|
||||
# madjong <madjong@i2pmail.org>, 2014
|
||||
msgid ""
|
||||
@@ -12,9 +12,9 @@ msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2014-12-17 17:00+0000\n"
|
||||
"Last-Translator: madjong <madjong@i2pmail.org>\n"
|
||||
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/projects/p/I2P/language/uk_UA/)\n"
|
||||
"PO-Revision-Date: 2015-08-07 16:31+0000\n"
|
||||
"Last-Translator: Denis Lysenko <gribua@gmail.com>\n"
|
||||
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/otf/I2P/language/uk_UA/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
@@ -25,7 +25,7 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Container of a byte array representing set and unset bits.
|
||||
@@ -66,7 +68,7 @@ public class BitField
|
||||
|
||||
/**
|
||||
* This returns the actual byte array used. Changes to this array
|
||||
* effect this BitField. Note that some bits at the end of the byte
|
||||
* affect this BitField. Note that some bits at the end of the byte
|
||||
* array are supposed to be always unset if they represent bits
|
||||
* bigger then the size of the bitfield.
|
||||
*/
|
||||
@@ -105,6 +107,37 @@ public class BitField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to false.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
* @since 0.9.22
|
||||
*/
|
||||
public void clear(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
synchronized(this) {
|
||||
if ((bitfield[index] & mask) != 0) {
|
||||
count--;
|
||||
bitfield[index] &= ~mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all bits to true.
|
||||
*
|
||||
* @since 0.9.21
|
||||
*/
|
||||
public void setAll() {
|
||||
Arrays.fill(bitfield, (byte) 0xff);
|
||||
count = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bit is set or false if it is not.
|
||||
*
|
||||
|
||||
@@ -90,17 +90,20 @@ abstract class ExtensionHandler {
|
||||
peer.setHandshakeMap(map);
|
||||
Map<String, BEValue> msgmap = map.get("m").getMap();
|
||||
|
||||
if (msgmap.get(TYPE_PEX) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer supports PEX extension: " + peer);
|
||||
// peer state calls peer listener calls sendPEX()
|
||||
}
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer " + peer + " supports extensions: " + msgmap.keySet());
|
||||
|
||||
if (msgmap.get(TYPE_DHT) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer supports DHT extension: " + peer);
|
||||
// peer state calls peer listener calls sendDHT()
|
||||
}
|
||||
//if (msgmap.get(TYPE_PEX) != null) {
|
||||
// if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Peer supports PEX extension: " + peer);
|
||||
// // peer state calls peer listener calls sendPEX()
|
||||
//}
|
||||
|
||||
//if (msgmap.get(TYPE_DHT) != null) {
|
||||
// if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Peer supports DHT extension: " + peer);
|
||||
// // peer state calls peer listener calls sendDHT()
|
||||
//}
|
||||
|
||||
MagnetState state = peer.getMagnetState();
|
||||
|
||||
@@ -204,30 +207,31 @@ abstract class ExtensionHandler {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Got request for " + piece + " from: " + peer);
|
||||
byte[] pc;
|
||||
int totalSize;
|
||||
synchronized(state) {
|
||||
pc = state.getChunk(piece);
|
||||
totalSize = state.getSize();
|
||||
}
|
||||
sendPiece(peer, piece, pc);
|
||||
sendPiece(peer, piece, pc, totalSize);
|
||||
// Do this here because PeerConnectionOut only reports for PIECE messages
|
||||
peer.uploaded(pc.length);
|
||||
listener.uploaded(peer, pc.length);
|
||||
} else if (type == TYPE_DATA) {
|
||||
int size = map.get("total_size").getInt();
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
// On close reading of BEP 9, this is the total metadata size.
|
||||
// Prior to 0.9.21, we sent the piece size, so we can't count on it.
|
||||
// just ignore it. The actual length will be verified in saveChunk()
|
||||
//int size = map.get("total_size").getInt();
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
boolean done;
|
||||
int chk = -1;
|
||||
synchronized(state) {
|
||||
if (state.isComplete())
|
||||
return;
|
||||
int len = is.available();
|
||||
if (len != size) {
|
||||
// probably fatal
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("total_size " + size + " but avail data " + len);
|
||||
}
|
||||
peer.downloaded(len);
|
||||
listener.downloaded(peer, len);
|
||||
// this checks the size
|
||||
done = state.saveChunk(piece, bs, bs.length - len, len);
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Got chunk " + piece + " from " + peer);
|
||||
@@ -290,11 +294,15 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data) {
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data, int totalSize) {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("msg_type", Integer.valueOf(TYPE_DATA));
|
||||
map.put("piece", Integer.valueOf(piece));
|
||||
map.put("total_size", Integer.valueOf(data.length));
|
||||
// BEP 9
|
||||
// "This key has the same semantics as the 'metadata_size' in the extension header"
|
||||
// which apparently means the same value. Fixed in 0.9.21.
|
||||
//map.put("total_size", Integer.valueOf(data.length));
|
||||
map.put("total_size", Integer.valueOf(totalSize));
|
||||
byte[] dict = BEncoder.bencode(map);
|
||||
byte[] payload = new byte[dict.length + data.length];
|
||||
System.arraycopy(dict, 0, payload, 0, dict.length);
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
@@ -255,6 +256,8 @@ public class I2PSnarkUtil {
|
||||
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
|
||||
if (opts.getProperty("i2p.streaming.answerPings") == null)
|
||||
opts.setProperty("i2p.streaming.answerPings", "false");
|
||||
if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null)
|
||||
opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
_connecting = false;
|
||||
}
|
||||
@@ -328,7 +331,7 @@ public class I2PSnarkUtil {
|
||||
return rv;
|
||||
} catch (I2PException ie) {
|
||||
_banlist.add(dest);
|
||||
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
_context.simpleTimer2().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
IOException ioe = new IOException("Unable to reach the peer " + peer);
|
||||
ioe.initCause(ie);
|
||||
throw ioe;
|
||||
@@ -456,7 +459,7 @@ public class I2PSnarkUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
public String getOurIPString() {
|
||||
Destination dest = getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
|
||||
@@ -55,11 +55,13 @@ class Message
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
// Also SUGGEST, REJECT, ALLOWED_FAST
|
||||
// low byte used for EXTENSION message
|
||||
// low two bytes used for PORT message
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
// Also REJECT
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
@@ -104,15 +106,18 @@ class Message
|
||||
int datalen = 1;
|
||||
|
||||
// piece is 4 bytes.
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
||||
datalen += 4;
|
||||
|
||||
// begin/offset is 4 bytes
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == REJECT)
|
||||
datalen += 4;
|
||||
|
||||
// length is 4 bytes
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
if (type == REQUEST || type == CANCEL ||
|
||||
type == REJECT)
|
||||
datalen += 4;
|
||||
|
||||
// msg type is 1 byte
|
||||
@@ -131,15 +136,18 @@ class Message
|
||||
dos.writeByte(type & 0xFF);
|
||||
|
||||
// Send additional info (piece number)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
||||
dos.writeInt(piece);
|
||||
|
||||
// Send additional info (begin/offset)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == REJECT)
|
||||
dos.writeInt(begin);
|
||||
|
||||
// Send additional info (length); for PIECE this is implicit.
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
if (type == REQUEST || type == CANCEL ||
|
||||
type == REJECT)
|
||||
dos.writeInt(length);
|
||||
|
||||
if (type == EXTENSION)
|
||||
@@ -173,21 +181,32 @@ class Message
|
||||
case UNINTERESTED:
|
||||
return "UNINTERESTED";
|
||||
case HAVE:
|
||||
return "HAVE(" + piece + ")";
|
||||
return "HAVE(" + piece + ')';
|
||||
case BITFIELD:
|
||||
return "BITFIELD";
|
||||
case REQUEST:
|
||||
return "REQUEST(" + piece + "," + begin + "," + length + ")";
|
||||
return "REQUEST(" + piece + ',' + begin + ',' + length + ')';
|
||||
case PIECE:
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
return "PIECE(" + piece + ',' + begin + ',' + length + ')';
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
return "CANCEL(" + piece + ',' + begin + ',' + length + ')';
|
||||
case PORT:
|
||||
return "PORT(" + piece + ")";
|
||||
return "PORT(" + piece + ')';
|
||||
case EXTENSION:
|
||||
return "EXTENSION(" + piece + ',' + data.length + ')';
|
||||
// fast extensions below here
|
||||
case SUGGEST:
|
||||
return "SUGGEST(" + piece + ')';
|
||||
case HAVE_ALL:
|
||||
return "HAVE_ALL";
|
||||
case HAVE_NONE:
|
||||
return "HAVE_NONE";
|
||||
case REJECT:
|
||||
return "REJECT(" + piece + ',' + begin + ',' + length + ')';
|
||||
case ALLOWED_FAST:
|
||||
return "ALLOWED_FAST(" + piece + ')';
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
return "UNKNOWN (" + type + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
|
||||
/**
|
||||
* Convert this PartialPiece to a request for the next chunk.
|
||||
* Used by PeerState only.
|
||||
* Used by PeerState only. This depends on the downloaded value
|
||||
* as set by setDownloaded() or read().
|
||||
*/
|
||||
|
||||
public Request getRequest() {
|
||||
@@ -128,14 +129,16 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
}
|
||||
|
||||
/**
|
||||
* How many bytes are good - only valid by setDownloaded()
|
||||
* How many bytes are good - as set by setDownloaded() or read()
|
||||
*/
|
||||
public int getDownloaded() {
|
||||
return this.off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this before returning a PartialPiece to the PeerCoordinator
|
||||
* Call this if necessary before returning a PartialPiece to the PeerCoordinator.
|
||||
* We do not use a bitmap to track individual chunks received.
|
||||
* Any chunks after a 'hole' will be lost.
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void setDownloaded(int offset) {
|
||||
@@ -191,11 +194,20 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* If offset matches the previous downloaded amount
|
||||
* (as set by a previous call to read() or setDownlaoded()),
|
||||
* the downloaded amount will be incremented by len.
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void read(DataInputStream din, int off, int len) throws IOException {
|
||||
public void read(DataInputStream din, int offset, int len) throws IOException {
|
||||
if (bs != null) {
|
||||
din.readFully(bs, off, len);
|
||||
din.readFully(bs, offset, len);
|
||||
synchronized (this) {
|
||||
// only works for in-order chunks
|
||||
if (this.off == offset)
|
||||
this.off += len;
|
||||
}
|
||||
} else {
|
||||
// read in fully before synching on raf
|
||||
ByteArray ba;
|
||||
@@ -211,8 +223,11 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
synchronized (this) {
|
||||
if (raf == null)
|
||||
createTemp();
|
||||
raf.seek(off);
|
||||
raf.seek(offset);
|
||||
raf.write(tmp);
|
||||
// only works for in-order chunks
|
||||
if (this.off == offset)
|
||||
this.off += len;
|
||||
}
|
||||
if (ba != null)
|
||||
_cache.release(ba, false);
|
||||
|
||||
@@ -79,15 +79,15 @@ public class Peer implements Comparable<Peer>
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
// bytes per bt spec: 0011223344556677
|
||||
static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
static final long OPTION_FAST = 0x0000000000000004l;
|
||||
static final long OPTION_DHT = 0x0000000000000001l;
|
||||
// bytes per bt spec: 0011223344556677
|
||||
private static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
private static final long OPTION_FAST = 0x0000000000000004l;
|
||||
//private static final long OPTION_DHT = 0x0000000000000001l;
|
||||
/** we use a different bit since the compact format is different */
|
||||
/* no, let's use an extension message
|
||||
static final long OPTION_I2P_DHT = 0x0000000040000000l;
|
||||
*/
|
||||
static final long OPTION_AZMP = 0x1000000000000000l;
|
||||
//private static final long OPTION_AZMP = 0x1000000000000000l;
|
||||
private long options;
|
||||
|
||||
/**
|
||||
@@ -297,7 +297,7 @@ public class Peer implements Comparable<Peer>
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
// Use this thread for running the incomming connection.
|
||||
// Use this thread for running the incoming connection.
|
||||
// The outgoing connection creates its own Thread.
|
||||
out.startup();
|
||||
Thread.currentThread().setName("Snark reader from " + peerID);
|
||||
@@ -338,6 +338,9 @@ public class Peer implements Comparable<Peer>
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - options
|
||||
long myOptions = OPTION_EXTENSION;
|
||||
// we can't handle HAVE_ALL or HAVE_NONE if we don't know the number of pieces
|
||||
if (metainfo != null)
|
||||
myOptions |= OPTION_FAST;
|
||||
// FIXME get util here somehow
|
||||
//if (util.getDHT() != null)
|
||||
// myOptions |= OPTION_I2P_DHT;
|
||||
@@ -385,15 +388,15 @@ public class Peer implements Comparable<Peer>
|
||||
if (options != 0) {
|
||||
// send them something in runConnection() above
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
|
||||
_log.debug("Peer supports options 0x" + Long.toHexString(options) + ": " + toString());
|
||||
}
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
public long getOptions() {
|
||||
return options;
|
||||
/** @since 0.9.21 */
|
||||
public boolean supportsFast() {
|
||||
return (options & OPTION_FAST) != 0;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
|
||||
@@ -98,44 +98,48 @@ class PeerConnectionIn implements Runnable
|
||||
}
|
||||
|
||||
byte b = din.readByte();
|
||||
Message m = new Message();
|
||||
m.type = b;
|
||||
switch (b)
|
||||
{
|
||||
case 0:
|
||||
case Message.CHOKE:
|
||||
ps.chokeMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received choke from " + peer);
|
||||
break;
|
||||
case 1:
|
||||
|
||||
case Message.UNCHOKE:
|
||||
ps.chokeMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unchoke from " + peer);
|
||||
break;
|
||||
case 2:
|
||||
|
||||
case Message.INTERESTED:
|
||||
ps.interestedMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received interested from " + peer);
|
||||
break;
|
||||
case 3:
|
||||
|
||||
case Message.UNINTERESTED:
|
||||
ps.interestedMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received not interested from " + peer);
|
||||
break;
|
||||
case 4:
|
||||
|
||||
case Message.HAVE:
|
||||
piece = din.readInt();
|
||||
ps.haveMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received havePiece(" + piece + ") from " + peer);
|
||||
break;
|
||||
case 5:
|
||||
|
||||
case Message.BITFIELD:
|
||||
byte[] bitmap = new byte[i-1];
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
|
||||
break;
|
||||
case 6:
|
||||
|
||||
case Message.REQUEST:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
@@ -143,7 +147,8 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received request(" + piece + "," + begin + ") from " + peer);
|
||||
break;
|
||||
case 7:
|
||||
|
||||
case Message.PIECE:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = i-9;
|
||||
@@ -165,7 +170,8 @@ class PeerConnectionIn implements Runnable
|
||||
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
|
||||
case Message.CANCEL:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
@@ -173,13 +179,15 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer);
|
||||
break;
|
||||
case 9: // PORT message
|
||||
|
||||
case Message.PORT:
|
||||
int port = din.readUnsignedShort();
|
||||
ps.portMessage(port);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received port message from " + peer);
|
||||
break;
|
||||
case 20: // Extension message
|
||||
|
||||
case Message.EXTENSION:
|
||||
int id = din.readUnsignedByte();
|
||||
byte[] payload = new byte[i-2];
|
||||
din.readFully(payload);
|
||||
@@ -187,6 +195,43 @@ class PeerConnectionIn implements Runnable
|
||||
_log.debug("Received extension message from " + peer);
|
||||
ps.extensionMessage(id, payload);
|
||||
break;
|
||||
|
||||
// fast extensions below here
|
||||
case Message.SUGGEST:
|
||||
piece = din.readInt();
|
||||
ps.suggestMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received suggest(" + piece + ") from " + peer);
|
||||
break;
|
||||
|
||||
case Message.HAVE_ALL:
|
||||
ps.haveMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received have_all from " + peer);
|
||||
break;
|
||||
|
||||
case Message.HAVE_NONE:
|
||||
ps.haveMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received have_none from " + peer);
|
||||
break;
|
||||
|
||||
case Message.REJECT:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.rejectMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received reject(" + piece + ',' + begin + ',' + len + ") from " + peer);
|
||||
break;
|
||||
|
||||
case Message.ALLOWED_FAST:
|
||||
piece = din.readInt();
|
||||
ps.allowedFastMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received allowed_fast(" + piece + ") from " + peer);
|
||||
break;
|
||||
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
|
||||
@@ -22,15 +22,15 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
//import net.i2p.util.SimpleScheduler;
|
||||
//import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
@@ -43,7 +43,7 @@ class PeerConnectionOut implements Runnable
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private final List<Message> sendQueue = new ArrayList<Message>();
|
||||
private final BlockingQueue<Message> sendQueue = new LinkedBlockingQueue<Message>();
|
||||
|
||||
private static final AtomicLong __id = new AtomicLong();
|
||||
private final long _id;
|
||||
@@ -125,6 +125,16 @@ class PeerConnectionOut implements Runnable
|
||||
if (state.choking) {
|
||||
it.remove();
|
||||
//SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
if (peer.supportsFast()) {
|
||||
Message r = new Message();
|
||||
r.type = Message.REJECT;
|
||||
r.piece = nm.piece;
|
||||
r.begin = nm.begin;
|
||||
r.length = nm.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + r);
|
||||
r.sendMessage(dout);
|
||||
}
|
||||
}
|
||||
nm = null;
|
||||
}
|
||||
@@ -142,8 +152,8 @@ class PeerConnectionOut implements Runnable
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && !sendQueue.isEmpty()) {
|
||||
m = sendQueue.remove(0);
|
||||
if (m == null) {
|
||||
m = sendQueue.poll();
|
||||
//SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
}
|
||||
@@ -160,6 +170,8 @@ class PeerConnectionOut implements Runnable
|
||||
lastSent = System.currentTimeMillis();
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
// FiXME this causes REJECT messages to be sent before sending the CHOKE;
|
||||
// BEP 6 recommends sending them after.
|
||||
if (m.type == Message.CHOKE)
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
@@ -234,7 +246,7 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -278,11 +290,22 @@ class PeerConnectionOut implements Runnable
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = it.next();
|
||||
if (m.type == type)
|
||||
{
|
||||
if (m.type == type) {
|
||||
it.remove();
|
||||
removed = true;
|
||||
}
|
||||
if (type == Message.PIECE && peer.supportsFast()) {
|
||||
Message r = new Message();
|
||||
r.type = Message.REJECT;
|
||||
r.piece = m.piece;
|
||||
r.begin = m.begin;
|
||||
r.length = m.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + r);
|
||||
try {
|
||||
r.sendMessage(dout);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
@@ -297,7 +320,7 @@ class PeerConnectionOut implements Runnable
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -350,12 +373,19 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
void sendBitfield(BitField bitfield)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
boolean fast = peer.supportsFast();
|
||||
if (fast && bitfield.complete()) {
|
||||
sendHaveAll();
|
||||
} else if (fast && bitfield.count() <= 0) {
|
||||
sendHaveNone();
|
||||
} else {
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
@@ -480,7 +510,6 @@ class PeerConnectionOut implements Runnable
|
||||
m.len = length;
|
||||
// since we have the data already loaded, queue a timeout to remove it
|
||||
// no longer prefetched
|
||||
//SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
@@ -511,7 +540,8 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all Request messages from the queue
|
||||
* Remove all Request messages from the queue.
|
||||
* Does not send a cancel message.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void cancelRequestMessages() {
|
||||
@@ -523,9 +553,12 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the PeerState when the other side doesn't want this
|
||||
// request to be handled anymore. Removes any pending Piece Message
|
||||
// from out send queue.
|
||||
/**
|
||||
* Called by the PeerState when the other side doesn't want this
|
||||
* request to be handled anymore. Removes any pending Piece Message
|
||||
* from out send queue.
|
||||
* Does not send a cancel message.
|
||||
*/
|
||||
void cancelRequest(int piece, int begin, int length)
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
@@ -561,4 +594,50 @@ class PeerConnectionOut implements Runnable
|
||||
m.piece = port;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void sendSuggest(int piece) {
|
||||
Message m = new Message();
|
||||
m.type = Message.SUGGEST;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
private void sendHaveAll() {
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE_ALL;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
private void sendHaveNone() {
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE_NONE;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
void sendReject(int piece, int begin, int length) {
|
||||
Message m = new Message();
|
||||
m.type = Message.REJECT;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void sendAllowedFast(int piece) {
|
||||
Message m = new Message();
|
||||
m.type = Message.ALLOWED_FAST;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
@@ -87,8 +88,8 @@ class PeerCoordinator implements PeerListener
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
private final AtomicLong uploaded = new AtomicLong();
|
||||
private final AtomicLong downloaded = new AtomicLong();
|
||||
final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
|
||||
private final long uploaded_old[] = {-1,-1,-1};
|
||||
private final long downloaded_old[] = {-1,-1,-1};
|
||||
@@ -279,7 +280,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
return uploaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +288,7 @@ class PeerCoordinator implements PeerListener
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public void setUploaded(long up) {
|
||||
uploaded = up;
|
||||
uploaded.set(up);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +296,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
return downloaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,16 +322,22 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
public long getUploadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getCurrentUploadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
// no need to synchronize, only one value
|
||||
long r = uploaded_old[0];
|
||||
if (r <= 0)
|
||||
@@ -913,6 +920,7 @@ class PeerCoordinator implements PeerListener
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*
|
||||
* @return bytes or null for errors such as not having the piece yet
|
||||
* @throws RuntimeException on IOE getting the data
|
||||
*/
|
||||
public ByteArray gotRequest(Peer peer, int piece, int off, int len)
|
||||
@@ -944,7 +952,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void uploaded(Peer peer, int size)
|
||||
{
|
||||
uploaded += size;
|
||||
uploaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@@ -955,7 +963,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void downloaded(Peer peer, int size)
|
||||
{
|
||||
downloaded += size;
|
||||
downloaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@@ -1003,9 +1011,21 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
else
|
||||
{
|
||||
// so we will try again
|
||||
markUnrequested(peer, piece);
|
||||
// just in case
|
||||
removePartialPiece(piece);
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
|
||||
// Mark this peer as not having the piece. PeerState will update its bitfield.
|
||||
for (Piece pc : wantedPieces) {
|
||||
if (pc.getId() == piece) {
|
||||
pc.removePeer(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
}
|
||||
@@ -1134,8 +1154,9 @@ class PeerCoordinator implements PeerListener
|
||||
*
|
||||
* Also mark the piece unrequested if this peer was the only one.
|
||||
*
|
||||
* @param peer partials, must include the zero-offset (empty) ones too
|
||||
* No dup pieces, piece.setDownloaded() must be set
|
||||
* @param peer partials, must include the zero-offset (empty) ones too.
|
||||
* No dup pieces, piece.setDownloaded() must be set.
|
||||
* len field in Requests is ignored.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public void savePartialPieces(Peer peer, List<Request> partials)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -155,12 +156,25 @@ class PeerState implements DataLoader
|
||||
setInteresting(true);
|
||||
}
|
||||
|
||||
void bitfieldMessage(byte[] bitmap)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv bitfield");
|
||||
void bitfieldMessage(byte[] bitmap) {
|
||||
bitfieldMessage(bitmap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmap null to use the isAll param
|
||||
* @param isAll only if bitmap == null: true for have_all, false for have_none
|
||||
* @since 0.9.21
|
||||
*/
|
||||
private void bitfieldMessage(byte[] bitmap, boolean isAll) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (bitmap != null)
|
||||
_log.debug(peer + " rcv bitfield bytes: " + bitmap.length);
|
||||
else if (isAll)
|
||||
_log.debug(peer + " rcv bitfield HAVE_ALL");
|
||||
else
|
||||
_log.debug(peer + " rcv bitfield HAVE_NONE");
|
||||
}
|
||||
synchronized(this) {
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you accept?
|
||||
@@ -172,10 +186,24 @@ class PeerState implements DataLoader
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
// FIXME will have to regenerate the bitfield after we know exactly
|
||||
// how many pieces there are, as we don't know how many spare bits there are.
|
||||
if (metainfo == null)
|
||||
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||
else
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
if (metainfo == null) {
|
||||
if (bitmap != null) {
|
||||
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||
} else {
|
||||
// we can't handle this situation
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("have_x w/o metainfo: " + isAll);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (bitmap != null) {
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
} else {
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
if (isAll)
|
||||
bitfield.setAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (metainfo == null)
|
||||
return;
|
||||
@@ -198,14 +226,21 @@ class PeerState implements DataLoader
|
||||
+ piece + ", " + begin + ", " + length + ") ");
|
||||
if (metainfo == null)
|
||||
return;
|
||||
if (choking)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Request received, but choking " + peer);
|
||||
if (choking) {
|
||||
if (peer.supportsFast()) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Request received, sending reject to choked " + peer);
|
||||
out.sendReject(piece, begin, length);
|
||||
} else {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Request received, but choking " + peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
// There is no check here that we actually have the piece;
|
||||
// this will be caught in loadData() below
|
||||
if (piece < 0
|
||||
|| piece >= metainfo.getPieces()
|
||||
|| begin < 0
|
||||
@@ -219,6 +254,8 @@ class PeerState implements DataLoader
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,8 +264,14 @@ class PeerState implements DataLoader
|
||||
// Todo: limit number of requests also? (robert 64 x 4KB)
|
||||
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
if (peer.supportsFast()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Rejecting request over pipeline limit from " + peer);
|
||||
out.sendReject(piece, begin, length);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,7 +286,8 @@ class PeerState implements DataLoader
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls
|
||||
*
|
||||
* @return bytes or null for errors
|
||||
* @return bytes or null for errors such as not having the piece yet
|
||||
* @throws RuntimeException on IOE getting the data
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public ByteArray loadData(int piece, int begin, int length) {
|
||||
@@ -253,6 +297,8 @@ class PeerState implements DataLoader
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -265,6 +311,8 @@ class PeerState implements DataLoader
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -322,6 +370,11 @@ class PeerState implements DataLoader
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
|
||||
synchronized(this) {
|
||||
// so we don't ask again
|
||||
if (bitfield != null)
|
||||
bitfield.clear(req.getPiece());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +508,12 @@ class PeerState implements DataLoader
|
||||
for (Integer p : pcs) {
|
||||
Request req = getLowestOutstandingRequest(p.intValue());
|
||||
if (req != null) {
|
||||
req.getPartialPiece().setDownloaded(req.off);
|
||||
PartialPiece pp = req.getPartialPiece();
|
||||
synchronized(pp) {
|
||||
int dl = pp.getDownloaded();
|
||||
if (req.off != dl)
|
||||
req = new Request(pp, dl, 1);
|
||||
}
|
||||
rv.add(req);
|
||||
}
|
||||
}
|
||||
@@ -536,6 +594,89 @@ class PeerState implements DataLoader
|
||||
listener.gotPort(peer, port, port + 1);
|
||||
}
|
||||
|
||||
/////////// fast message handlers /////////
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* Treated as "have" for now
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void suggestMessage(int piece) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Handling suggest as have(" + piece + ") from " + peer);
|
||||
haveMessage(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* @param isAll true for have_all, false for have_none
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void haveMessage(boolean isAll) {
|
||||
bitfieldMessage(null, isAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* If the peer rejects lower chunks but not higher ones, thus creating holes,
|
||||
* we won't figure it out and the piece will fail, since we don't currently
|
||||
* keep a chunk bitmap in PartialPiece.
|
||||
* As long as the peer rejects all the chunks, or rejects only the last chunks,
|
||||
* no holes are created and we will be fine. The reject messages may be in any order,
|
||||
* just don't make a hole when it's over.
|
||||
*
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void rejectMessage(int piece, int begin, int length) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||
out.cancelRequest(piece, begin, length);
|
||||
synchronized(this) {
|
||||
Request deletedRequest = null;
|
||||
// for this piece only
|
||||
boolean haveMoreRequests = false;
|
||||
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
|
||||
Request req = iter.next();
|
||||
if (req.getPiece() == piece) {
|
||||
if (req.off == begin && req.len == length) {
|
||||
iter.remove();
|
||||
deletedRequest = req;
|
||||
} else {
|
||||
haveMoreRequests = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deletedRequest != null && !haveMoreRequests) {
|
||||
// We must return the piece to the coordinator
|
||||
// Create a new fake request so we can set the offset correctly
|
||||
PartialPiece pp = deletedRequest.getPartialPiece();
|
||||
int downloaded = pp.getDownloaded();
|
||||
Request req;
|
||||
if (deletedRequest.off == downloaded)
|
||||
req = deletedRequest;
|
||||
else
|
||||
req = new Request(pp, downloaded, 1);
|
||||
List<Request> pcs = Collections.singletonList(req);
|
||||
listener.savePartialPieces(this.peer, pcs);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Returned to coord. w/ offset " + pp.getDownloaded() + " due to reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||
}
|
||||
if (lastRequest != null && lastRequest.getPiece() == piece &&
|
||||
lastRequest.off == begin && lastRequest.len == length)
|
||||
lastRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* Ignored for now
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void allowedFastMessage(int piece) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Ignoring allowed_fast(" + piece + ") from " + peer);
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -543,6 +684,8 @@ class PeerState implements DataLoader
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
/////////// end message handlers /////////
|
||||
|
||||
/**
|
||||
* We now have this piece.
|
||||
* Tell the peer and cancel any requests for the piece.
|
||||
|
||||
@@ -43,13 +43,13 @@ class Request
|
||||
*/
|
||||
Request(PartialPiece piece, int off, int len)
|
||||
{
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
|
||||
this.piece = piece;
|
||||
this.off = off;
|
||||
this.len = len;
|
||||
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -290,6 +290,7 @@ public class Snark
|
||||
|
||||
/**
|
||||
* multitorrent
|
||||
* @throws RuntimeException via fatal()
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
@@ -304,6 +305,7 @@ public class Snark
|
||||
* multitorrent
|
||||
*
|
||||
* @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
|
||||
* @throws RuntimeException via fatal()
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
@@ -478,6 +480,7 @@ public class Snark
|
||||
* @param torrent a fake name for now (not a file name)
|
||||
* @param ih 20-byte info hash
|
||||
* @param trackerURL may be null
|
||||
* @throws RuntimeException via fatal()
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, byte[] ih, String trackerURL,
|
||||
@@ -531,6 +534,8 @@ public class Snark
|
||||
/**
|
||||
* Start up contacting peers and querying the tracker.
|
||||
* Blocks if tunnel is not yet open.
|
||||
*
|
||||
* @throws RuntimeException via fatal()
|
||||
*/
|
||||
public synchronized void startTorrent() {
|
||||
starting = true;
|
||||
@@ -612,7 +617,6 @@ public class Snark
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public synchronized void stopTorrent(boolean fast) {
|
||||
stopped = true;
|
||||
TrackerClient tc = trackerclient;
|
||||
if (tc != null)
|
||||
tc.halt(fast);
|
||||
@@ -620,17 +624,28 @@ public class Snark
|
||||
if (pc != null)
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (!fast)
|
||||
// HACK: Needed a way to distinguish between user-stop and
|
||||
// shutdown-stop. stopTorrent(true) is in stopAllTorrents().
|
||||
// (#766)
|
||||
stopped = true;
|
||||
if (st != null) {
|
||||
boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
|
||||
// TODO: Cache the config-in-mem to compare vs config-on-disk
|
||||
// (needed for auto-save to not double-save in some cases)
|
||||
//boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
|
||||
boolean changed = true;
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
}
|
||||
if (fast)
|
||||
// HACK: See above if(!fast)
|
||||
stopped = true;
|
||||
if (pc != null && _peerCoordinatorSet != null)
|
||||
_peerCoordinatorSet.remove(pc);
|
||||
if (_peerCoordinatorSet == null)
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.crypto.SHA1Hash;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
@@ -88,6 +89,7 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
private static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
private static final String PROP_META_RUNNING = "running";
|
||||
private static final String PROP_META_STAMP = "stamp";
|
||||
private static final String PROP_META_BASE = "base";
|
||||
private static final String PROP_META_BITFIELD = "bitfield";
|
||||
@@ -233,7 +235,7 @@ public class SnarkManager implements CompleteListener {
|
||||
// only if default instance
|
||||
if ("i2psnark".equals(_contextName))
|
||||
// delay until UpdateManager is there
|
||||
_context.simpleScheduler().addEvent(new Register(), 4*60*1000);
|
||||
_context.simpleTimer2().addEvent(new Register(), 4*60*1000);
|
||||
// Not required, Jetty has a shutdown hook
|
||||
//_context.addShutdownTask(new SnarkManagerShutdown());
|
||||
_idleChecker = new IdleChecker(this, _peerCoordinatorSet);
|
||||
@@ -537,6 +539,27 @@ public class SnarkManager implements CompleteListener {
|
||||
return new File(subdir, hex + CONFIG_FILE_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the info hash from a config file name
|
||||
* @return null for invalid name
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private static SHA1Hash configFileToInfoHash(File file) {
|
||||
String name = file.getName();
|
||||
if (name.length() != 40 + CONFIG_FILE_SUFFIX.length() || !name.endsWith(CONFIG_FILE_SUFFIX))
|
||||
return null;
|
||||
String hex = name.substring(0, 40);
|
||||
byte[] ih = new byte[20];
|
||||
try {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ih[i] = (byte) (Integer.parseInt(hex.substring(i*2, (i*2) + 2), 16) & 0xff);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
return null;
|
||||
}
|
||||
return new SHA1Hash(ih);
|
||||
}
|
||||
|
||||
/** null to set initial defaults */
|
||||
public void loadConfig(String filename) {
|
||||
synchronized(_configLock) {
|
||||
@@ -1224,7 +1247,7 @@ public class SnarkManager implements CompleteListener {
|
||||
_log.info("New Snark, torrent: " + filename + " base: " + baseFile);
|
||||
torrent = new Snark(_util, filename, null, -1, null, null, this,
|
||||
_peerCoordinatorSet, _connectionAcceptor,
|
||||
false, dataDir.getPath(), baseFile);
|
||||
shouldAutoStart(), dataDir.getPath(), baseFile);
|
||||
loadSavedFilePriorities(torrent);
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
@@ -1248,7 +1271,16 @@ public class SnarkManager implements CompleteListener {
|
||||
return;
|
||||
}
|
||||
// ok, snark created, now lets start it up or configure it further
|
||||
if (!dontAutoStart && shouldAutoStart()) {
|
||||
Properties config = getConfig(torrent);
|
||||
boolean running;
|
||||
String prop = config.getProperty(PROP_META_RUNNING);
|
||||
if(prop == null || Boolean.parseBoolean(prop)) {
|
||||
running = true;
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
// Were we running last time?
|
||||
if (!dontAutoStart && shouldAutoStart() && running) {
|
||||
torrent.startTorrent();
|
||||
addMessage(_("Torrent added and started: \"{0}\"", torrent.getBaseName()));
|
||||
} else {
|
||||
@@ -1356,6 +1388,7 @@ public class SnarkManager implements CompleteListener {
|
||||
snark.stopTorrent();
|
||||
_magnets.remove(snark.getName());
|
||||
removeMagnetStatus(snark.getInfoHash());
|
||||
removeTorrentStatus(snark);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1406,9 +1439,10 @@ public class SnarkManager implements CompleteListener {
|
||||
if (snark != null) {
|
||||
addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
|
||||
return false;
|
||||
} else {
|
||||
saveTorrentStatus(metainfo, bitfield, null, baseFile, true, 0, true); // no file priorities
|
||||
}
|
||||
// so addTorrent won't recheck
|
||||
saveTorrentStatus(metainfo, bitfield, null, baseFile, true, 0); // no file priorities
|
||||
// so addTorrent won't recheck
|
||||
try {
|
||||
locked_writeMetaInfo(metainfo, filename, areFilesPublic());
|
||||
// hold the lock for a long time
|
||||
@@ -1603,7 +1637,7 @@ public class SnarkManager implements CompleteListener {
|
||||
return;
|
||||
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(),
|
||||
storage.getBase(), storage.getPreserveFileNames(),
|
||||
snark.getUploaded());
|
||||
snark.getUploaded(), snark.isStopped());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1618,14 +1652,14 @@ public class SnarkManager implements CompleteListener {
|
||||
* @param base may be null
|
||||
*/
|
||||
private void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities,
|
||||
File base, boolean preserveNames, long uploaded) {
|
||||
File base, boolean preserveNames, long uploaded, boolean stopped) {
|
||||
synchronized (_configLock) {
|
||||
locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames, uploaded);
|
||||
locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames, uploaded, stopped);
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities,
|
||||
File base, boolean preserveNames, long uploaded) {
|
||||
File base, boolean preserveNames, long uploaded, boolean stopped) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String bfs;
|
||||
if (bitfield.complete()) {
|
||||
@@ -1634,11 +1668,13 @@ public class SnarkManager implements CompleteListener {
|
||||
byte[] bf = bitfield.getFieldBytes();
|
||||
bfs = Base64.encode(bf);
|
||||
}
|
||||
boolean running = !stopped;
|
||||
Properties config = getConfig(ih);
|
||||
config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
|
||||
config.setProperty(PROP_META_BITFIELD, bfs);
|
||||
config.setProperty(PROP_META_PRESERVE_NAMES, Boolean.toString(preserveNames));
|
||||
config.setProperty(PROP_META_UPLOADED, Long.toString(uploaded));
|
||||
config.setProperty(PROP_META_RUNNING, Boolean.toString(running));
|
||||
if (base != null)
|
||||
config.setProperty(PROP_META_BASE, base.getAbsolutePath());
|
||||
|
||||
@@ -1684,11 +1720,18 @@ public class SnarkManager implements CompleteListener {
|
||||
/**
|
||||
* Remove the status of a torrent by removing the config file.
|
||||
*/
|
||||
public void removeTorrentStatus(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
private void removeTorrentStatus(Snark snark) {
|
||||
byte[] ih = snark.getInfoHash();
|
||||
File conf = configFile(_configDir, ih);
|
||||
synchronized (_configLock) {
|
||||
conf.delete();
|
||||
boolean ok = conf.delete();
|
||||
if (ok) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Deleted " + conf + " for " + snark.getName());
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Failed to delete " + conf + " for " + snark.getName());
|
||||
}
|
||||
File subdir = conf.getParentFile();
|
||||
String[] files = subdir.list();
|
||||
if (files != null && files.length == 0)
|
||||
@@ -1696,6 +1739,62 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all orphaned torrent status files, which weren't removed
|
||||
* before 0.9.20, and could be left around after a manual delete also.
|
||||
* Run this once at startup.
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private void cleanupTorrentStatus() {
|
||||
Set<SHA1Hash> torrents = new HashSet<SHA1Hash>(32);
|
||||
int found = 0;
|
||||
int totalDeleted = 0;
|
||||
synchronized (_snarks) {
|
||||
for (Snark snark : _snarks.values()) {
|
||||
torrents.add(new SHA1Hash(snark.getInfoHash()));
|
||||
}
|
||||
synchronized (_configLock) {
|
||||
for (int i = 0; i < B64.length(); i++) {
|
||||
File subdir = new File(_configDir, SUBDIR_PREFIX + B64.charAt(i));
|
||||
File[] configs = subdir.listFiles();
|
||||
if (configs == null)
|
||||
continue;
|
||||
int deleted = 0;
|
||||
for (int j = 0; j < configs.length; j++) {
|
||||
File config = configs[j];
|
||||
SHA1Hash ih = configFileToInfoHash(config);
|
||||
if (ih == null)
|
||||
continue;
|
||||
found++;
|
||||
if (torrents.contains(ih)) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Torrent for " + config + " exists");
|
||||
} else {
|
||||
boolean ok = config.delete();
|
||||
if (ok) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Deleted " + config + " for " + ih);
|
||||
deleted++;
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Failed to delete " + config + " for " + ih);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleted == configs.length) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Deleting " + subdir);
|
||||
subdir.delete();
|
||||
}
|
||||
totalDeleted += deleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Cleanup found " + torrents.size() + " torrents and " + found +
|
||||
" configs, deleted " + totalDeleted + " old configs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Just remember we have it
|
||||
* @since 0.8.4
|
||||
@@ -1753,7 +1852,8 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the torrent, leaving it on the list of torrents unless told to remove it
|
||||
* Stop the torrent, leaving it on the list of torrents unless told to remove it.
|
||||
* If shouldRemove is true, removes the config file also.
|
||||
*/
|
||||
public Snark stopTorrent(String filename, boolean shouldRemove) {
|
||||
File sfile = new File(filename);
|
||||
@@ -1781,6 +1881,8 @@ public class SnarkManager implements CompleteListener {
|
||||
// I2PServerSocket.accept() call properly?)
|
||||
////_util.
|
||||
}
|
||||
if (shouldRemove)
|
||||
removeTorrentStatus(torrent);
|
||||
if (!wasStopped)
|
||||
addMessage(_("Torrent stopped: \"{0}\"", torrent.getBaseName()));
|
||||
}
|
||||
@@ -1788,7 +1890,8 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the torrent, leaving it on the list of torrents unless told to remove it
|
||||
* Stop the torrent, leaving it on the list of torrents unless told to remove it.
|
||||
* If shouldRemove is true, removes the config file also.
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void stopTorrent(Snark torrent, boolean shouldRemove) {
|
||||
@@ -1801,11 +1904,13 @@ public class SnarkManager implements CompleteListener {
|
||||
torrent.stopTorrent();
|
||||
if (!wasStopped)
|
||||
addMessage(_("Torrent stopped: \"{0}\"", torrent.getBaseName()));
|
||||
if (shouldRemove)
|
||||
removeTorrentStatus(torrent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the torrent and delete the torrent file itself, but leaving the data
|
||||
* behind.
|
||||
* behind. Removes saved config file also.
|
||||
* Holds the snarks lock to prevent interference from the DirMonitor.
|
||||
*/
|
||||
public void removeTorrent(String filename) {
|
||||
@@ -1818,9 +1923,6 @@ public class SnarkManager implements CompleteListener {
|
||||
File torrentFile = new File(filename);
|
||||
torrentFile.delete();
|
||||
}
|
||||
Storage storage = torrent.getStorage();
|
||||
if (storage != null)
|
||||
removeTorrentStatus(storage.getMetaInfo());
|
||||
addMessage(_("Torrent removed: \"{0}\"", torrent.getBaseName()));
|
||||
}
|
||||
|
||||
@@ -1853,6 +1955,7 @@ public class SnarkManager implements CompleteListener {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
}
|
||||
if (doMagnets) {
|
||||
// first run only
|
||||
try {
|
||||
addMagnets();
|
||||
doMagnets = false;
|
||||
@@ -1861,6 +1964,9 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
if (!_snarks.isEmpty())
|
||||
addMessage(_("Up bandwidth limit is {0} KBps", _util.getMaxUpBW()));
|
||||
// To fix bug where files were left behind,
|
||||
// but also good for when user removes snarks when i2p is not running
|
||||
cleanupTorrentStatus();
|
||||
}
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
@@ -1883,7 +1989,8 @@ public class SnarkManager implements CompleteListener {
|
||||
if (meta.getFiles() != null)
|
||||
buf.append('/');
|
||||
buf.append("\">").append(base).append("</a>");
|
||||
addMessageNoEscape(_("Download finished: {0}", buf.toString())); // + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')');
|
||||
if (snark.getDownloaded() > 0)
|
||||
addMessageNoEscape(_("Download finished: {0}", buf.toString())); // + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')');
|
||||
updateStatus(snark);
|
||||
}
|
||||
|
||||
@@ -1895,7 +2002,8 @@ public class SnarkManager implements CompleteListener {
|
||||
Storage storage = snark.getStorage();
|
||||
if (meta != null && storage != null)
|
||||
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(),
|
||||
storage.getBase(), storage.getPreserveFileNames(), snark.getUploaded());
|
||||
storage.getBase(), storage.getPreserveFileNames(), snark.getUploaded(),
|
||||
snark.isStopped());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1918,7 +2026,8 @@ public class SnarkManager implements CompleteListener {
|
||||
return null;
|
||||
}
|
||||
saveTorrentStatus(meta, storage.getBitField(), null,
|
||||
storage.getBase(), storage.getPreserveFileNames(), 0);
|
||||
storage.getBase(), storage.getPreserveFileNames(), 0,
|
||||
snark.isStopped());
|
||||
// temp for addMessage() in case canonical throws
|
||||
String name = storage.getBaseName();
|
||||
try {
|
||||
@@ -2241,7 +2350,7 @@ public class SnarkManager implements CompleteListener {
|
||||
* Stop all running torrents, and close the tunnel after a delay
|
||||
* to allow for announces.
|
||||
* If called at router shutdown via Jetty shutdown hook -> webapp destroy() -> stop(),
|
||||
* the tunnel won't actually be closed as the SimpleScheduler is already shutdown
|
||||
* the tunnel won't actually be closed as the SimpleTimer2 is already shutdown
|
||||
* or will be soon, so we delay a few seconds inline.
|
||||
* @param finalShutdown if true, sleep at the end if any torrents were running
|
||||
* @since 0.9.1
|
||||
@@ -2272,7 +2381,7 @@ public class SnarkManager implements CompleteListener {
|
||||
dht.stop();
|
||||
// Schedule this even for final shutdown, as there's a chance
|
||||
// that it's just this webapp that is stopping.
|
||||
_context.simpleScheduler().addEvent(new Disconnector(), 60*1000);
|
||||
_context.simpleTimer2().addEvent(new Disconnector(), 60*1000);
|
||||
addMessage(_("Closing I2P tunnel after notifying trackers."));
|
||||
if (finalShutdown) {
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -50,7 +51,7 @@ import net.i2p.util.SystemVersion;
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
*/
|
||||
public class Storage
|
||||
public class Storage implements Closeable
|
||||
{
|
||||
private final MetaInfo metainfo;
|
||||
private final List<TorrentFile> _torrentFiles;
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* Holds different types that a bencoded byte array can represent.
|
||||
@@ -208,7 +209,7 @@ public class BEValue
|
||||
} else if (bin) {
|
||||
buf.append(bs.length).append(" bytes: ").append(Base64.encode(bs));
|
||||
} else {
|
||||
buf.append('"').append(new String(bs)).append('"');
|
||||
buf.append('"').append(DataHelper.getUTF8(bs)).append('"');
|
||||
}
|
||||
valueString = buf.toString();
|
||||
} else
|
||||
|
||||
@@ -87,6 +87,8 @@ abstract class PersistDHT {
|
||||
out.println(ni.toPersistentString());
|
||||
count++;
|
||||
}
|
||||
if (out.checkError())
|
||||
throw new IOException("Failed write to " + file);
|
||||
} catch (IOException ioe) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Error writing the DHT File", ioe);
|
||||
|
||||
@@ -457,6 +457,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write(" <a href=\"" + _contextPath + '/');
|
||||
if (peerParam != null) {
|
||||
// disable peer view
|
||||
out.write(getQueryString(req, "", null, null));
|
||||
out.write("\">");
|
||||
tx = _("Hide Peers");
|
||||
out.write(toThemeImg("hidepeers", tx, tx));
|
||||
@@ -686,6 +687,17 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write(", ");
|
||||
out.write(ngettext("1 DHT peer", "{0} DHT peers", dhts));
|
||||
}
|
||||
}
|
||||
String IPString = _manager.util().getOurIPString();
|
||||
if(!IPString.equals("unknown")) {
|
||||
// Only truncate if it's an actual dest
|
||||
out.write("; ");
|
||||
out.write(_("Dest"));
|
||||
out.write(": <tt>");
|
||||
out.write(IPString.substring(0, 4));
|
||||
out.write("</tt>");
|
||||
}
|
||||
if (dht != null) {
|
||||
if (showDebug)
|
||||
out.write(dht.renderStatusHTML());
|
||||
}
|
||||
@@ -1047,19 +1059,20 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
File f = new File(name);
|
||||
f.delete();
|
||||
_manager.addMessage(_("Torrent file deleted: {0}", f.getAbsolutePath()));
|
||||
List<List<String>> files = meta.getFiles();
|
||||
String dataFile = snark.getBaseName();
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
if (files == null) { // single file torrent
|
||||
if (f.delete())
|
||||
_manager.addMessage(_("Data file deleted: {0}", f.getAbsolutePath()));
|
||||
else
|
||||
_manager.addMessage(_("Data file could not be deleted: {0}", f.getAbsolutePath()));
|
||||
break;
|
||||
}
|
||||
Storage storage = snark.getStorage();
|
||||
if (storage == null)
|
||||
break;
|
||||
List<List<String>> files = meta.getFiles();
|
||||
if (files == null) { // single file torrent
|
||||
for (File df : storage.getFiles()) {
|
||||
// should be only one
|
||||
if (df.delete())
|
||||
_manager.addMessage(_("Data file deleted: {0}", df.getAbsolutePath()));
|
||||
else
|
||||
_manager.addMessage(_("Data file could not be deleted: {0}", df.getAbsolutePath()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
// step 1 delete files
|
||||
for (File df : storage.getFiles()) {
|
||||
if (df.delete()) {
|
||||
@@ -2269,13 +2282,13 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Inbound Settings"));
|
||||
out.write(":<td>");
|
||||
out.write(renderOptions(1, 8, 3, options.remove("inbound.quantity"), "inbound.quantity", TUNNEL));
|
||||
out.write(renderOptions(1, 10, 3, options.remove("inbound.quantity"), "inbound.quantity", TUNNEL));
|
||||
out.write(" ");
|
||||
out.write(renderOptions(0, 4, 3, options.remove("inbound.length"), "inbound.length", HOP));
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Outbound Settings"));
|
||||
out.write(":<td>");
|
||||
out.write(renderOptions(1, 8, 3, options.remove("outbound.quantity"), "outbound.quantity", TUNNEL));
|
||||
out.write(renderOptions(1, 10, 3, options.remove("outbound.quantity"), "outbound.quantity", TUNNEL));
|
||||
out.write(" ");
|
||||
out.write(renderOptions(0, 4, 3, options.remove("outbound.length"), "outbound.length", HOP));
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -26,73 +26,14 @@
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- this webapp doesn't actually use sessions or cookies -->
|
||||
<session-config>
|
||||
<session-timeout>
|
||||
30
|
||||
</session-timeout>
|
||||
<cookie-config>
|
||||
<http-only>true</http-only>
|
||||
</cookie-config>
|
||||
</session-config>
|
||||
|
||||
|
||||
<!-- mime types not in mime.properties in the jetty 5.1.15 source -->
|
||||
|
||||
<mime-mapping>
|
||||
<extension>mkv</extension>
|
||||
<mime-type>video/x-matroska</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>wmv</extension>
|
||||
<mime-type>video/x-ms-wmv</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>flv</extension>
|
||||
<mime-type>video/x-flv</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>mp4</extension>
|
||||
<mime-type>video/mp4</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>rar</extension>
|
||||
<mime-type>application/rar</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>7z</extension>
|
||||
<mime-type>application/x-7z-compressed</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>iso</extension>
|
||||
<mime-type>application/x-iso9660-image</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>ico</extension>
|
||||
<mime-type>image/x-icon</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>exe</extension>
|
||||
<mime-type>application/x-msdos-program</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>flac</extension>
|
||||
<mime-type>audio/flac</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>m4a</extension>
|
||||
<mime-type>audio/mpeg</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>wma</extension>
|
||||
<mime-type>audio/x-ms-wma</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
</web-app>
|
||||
|
||||
@@ -25,7 +25,7 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
|
||||
@@ -24,7 +24,7 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* Gunzip implementation per
|
||||
* <a href="http://www.faqs.org/rfcs/rfc1952.html">RFC 1952</a>, reusing
|
||||
* java's standard CRC32 and Inflater and InflaterOutputStream implementations.
|
||||
*
|
||||
* Note that the underlying InflaterOutputStream cannot be reused after close(),
|
||||
* so we don't have a Reusable version of this.
|
||||
*
|
||||
* Modified from net.i2p.util.ResettableGZIPInputStream to use Java 6 InflaterOutputstream
|
||||
* @since 0.9.21
|
||||
*/
|
||||
class GunzipOutputStream extends InflaterOutputStream {
|
||||
private static final int FOOTER_SIZE = 8; // CRC32 + ISIZE
|
||||
private final CRC32 _crc32;
|
||||
private final byte _buf1[] = new byte[1];
|
||||
private boolean _complete;
|
||||
private final byte _footer[] = new byte[FOOTER_SIZE];
|
||||
private long _bytesReceived;
|
||||
private long _bytesReceivedAtCompletion;
|
||||
|
||||
private enum HeaderState { MB1, MB2, CF, MT0, MT1, MT2, MT3, EF, OS, FLAGS,
|
||||
EH1, EH2, EHDATA, NAME, COMMENT, CRC1, CRC2, DONE }
|
||||
private HeaderState _state = HeaderState.MB1;
|
||||
private int _flags;
|
||||
private int _extHdrToRead;
|
||||
|
||||
/**
|
||||
* Build a new Gunzip stream
|
||||
*/
|
||||
public GunzipOutputStream(OutputStream uncompressedStream) throws IOException {
|
||||
super(uncompressedStream, new Inflater(true));
|
||||
_crc32 = new CRC32();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
_buf1[0] = (byte) b;
|
||||
write(_buf1, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte buf[]) throws IOException {
|
||||
write(buf, 0, buf.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
if (_complete) {
|
||||
// shortcircuit so the inflater doesn't try to refill
|
||||
// with the footer's data (which would fail, causing ZLIB err)
|
||||
return;
|
||||
}
|
||||
boolean isFinished = inf.finished();
|
||||
for (int i = off; i < off + len; i++) {
|
||||
if (!isFinished) {
|
||||
if (_state != HeaderState.DONE) {
|
||||
verifyHeader(buf[i]);
|
||||
continue;
|
||||
}
|
||||
// ensure we call the same method variant so we don't depend on underlying implementation
|
||||
super.write(buf, i, 1);
|
||||
if (inf.finished()) {
|
||||
isFinished = true;
|
||||
_bytesReceivedAtCompletion = _bytesReceived;
|
||||
}
|
||||
}
|
||||
_footer[(int) (_bytesReceived++ % FOOTER_SIZE)] = buf[i];
|
||||
if (isFinished) {
|
||||
long footerSize = _bytesReceivedAtCompletion - _bytesReceived;
|
||||
// could be at 7 or 8...
|
||||
// we write the first byte of the footer to the Inflater if necessary...
|
||||
// see comments in ResettableGZIPInputStream for details
|
||||
if (footerSize >= FOOTER_SIZE - 1) {
|
||||
try {
|
||||
verifyFooter();
|
||||
inf.reset(); // so it doesn't bitch about missing data...
|
||||
_complete = true;
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
// failed at 7, retry at 8
|
||||
if (footerSize == FOOTER_SIZE - 1 && i < off + len - 1)
|
||||
continue;
|
||||
_complete = true;
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflater statistic
|
||||
*/
|
||||
public long getTotalRead() {
|
||||
try {
|
||||
return inf.getBytesRead();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflater statistic
|
||||
*/
|
||||
public long getTotalExpanded() {
|
||||
try {
|
||||
return inf.getBytesWritten();
|
||||
} catch (Exception e) {
|
||||
// possible NPE in some implementations
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflater statistic
|
||||
*/
|
||||
public long getRemaining() {
|
||||
try {
|
||||
return inf.getRemaining();
|
||||
} catch (Exception e) {
|
||||
// possible NPE in some implementations
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflater statistic
|
||||
*/
|
||||
public boolean getFinished() {
|
||||
try {
|
||||
return inf.finished();
|
||||
} catch (Exception e) {
|
||||
// possible NPE in some implementations
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
_complete = true;
|
||||
_state = HeaderState.DONE;
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GOS read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException on CRC or length check fail
|
||||
*/
|
||||
private void verifyFooter() throws IOException {
|
||||
int idx = (int) (_bytesReceivedAtCompletion % FOOTER_SIZE);
|
||||
byte[] footer;
|
||||
if (idx == 0) {
|
||||
footer = _footer;
|
||||
} else {
|
||||
footer = new byte[FOOTER_SIZE];
|
||||
for (int i = 0; i < FOOTER_SIZE; i++) {
|
||||
footer[i] = _footer[(int) ((_bytesReceivedAtCompletion + i) % FOOTER_SIZE)];
|
||||
}
|
||||
}
|
||||
|
||||
long actualSize = inf.getTotalOut();
|
||||
long expectedSize = DataHelper.fromLongLE(footer, 4, 4);
|
||||
if (expectedSize != actualSize)
|
||||
throw new IOException("gunzip expected " + expectedSize + " bytes, got " + actualSize);
|
||||
|
||||
long actualCRC = _crc32.getValue();
|
||||
long expectedCRC = DataHelper.fromLongLE(footer, 0, 4);
|
||||
if (expectedCRC != actualCRC)
|
||||
throw new IOException("gunzip CRC fail expected 0x" + Long.toHexString(expectedCRC) +
|
||||
" bytes, got 0x" + Long.toHexString(actualCRC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the header is valid, throwing an IOException if it is bad.
|
||||
* Pushes through the state machine, checking as we go.
|
||||
* Call for each byte until HeaderState is DONE.
|
||||
*/
|
||||
private void verifyHeader(byte b) throws IOException {
|
||||
int c = b & 0xff;
|
||||
switch (_state) {
|
||||
case MB1:
|
||||
if (c != 0x1F) throw new IOException("First magic byte was wrong [" + c + "]");
|
||||
_state = HeaderState.MB2;
|
||||
break;
|
||||
|
||||
case MB2:
|
||||
if (c != 0x8B) throw new IOException("Second magic byte was wrong [" + c + "]");
|
||||
_state = HeaderState.CF;
|
||||
break;
|
||||
|
||||
case CF:
|
||||
if (c != 0x08) throw new IOException("Compression format is invalid [" + c + "]");
|
||||
_state = HeaderState.FLAGS;
|
||||
break;
|
||||
|
||||
case FLAGS:
|
||||
_flags = c;
|
||||
_state = HeaderState.MT0;
|
||||
break;
|
||||
|
||||
case MT0:
|
||||
// ignore
|
||||
_state = HeaderState.MT1;
|
||||
break;
|
||||
|
||||
case MT1:
|
||||
// ignore
|
||||
_state = HeaderState.MT2;
|
||||
break;
|
||||
|
||||
case MT2:
|
||||
// ignore
|
||||
_state = HeaderState.MT3;
|
||||
break;
|
||||
|
||||
case MT3:
|
||||
// ignore
|
||||
_state = HeaderState.EF;
|
||||
break;
|
||||
|
||||
case EF:
|
||||
if ( (c != 0x00) && (c != 0x02) && (c != 0x04) )
|
||||
throw new IOException("Invalid extended flags [" + c + "]");
|
||||
_state = HeaderState.OS;
|
||||
break;
|
||||
|
||||
case OS:
|
||||
// ignore
|
||||
if (0 != (_flags & (1<<5)))
|
||||
_state = HeaderState.EH1;
|
||||
else if (0 != (_flags & (1<<4)))
|
||||
_state = HeaderState.NAME;
|
||||
else if (0 != (_flags & (1<<3)))
|
||||
_state = HeaderState.COMMENT;
|
||||
else if (0 != (_flags & (1<<6)))
|
||||
_state = HeaderState.CRC1;
|
||||
else
|
||||
_state = HeaderState.DONE;
|
||||
break;
|
||||
|
||||
case EH1:
|
||||
_extHdrToRead = c;
|
||||
_state = HeaderState.EH2;
|
||||
break;
|
||||
|
||||
case EH2:
|
||||
_extHdrToRead += (c << 8);
|
||||
if (_extHdrToRead > 0)
|
||||
_state = HeaderState.EHDATA;
|
||||
else if (0 != (_flags & (1<<4)))
|
||||
_state = HeaderState.NAME;
|
||||
if (0 != (_flags & (1<<3)))
|
||||
_state = HeaderState.COMMENT;
|
||||
else if (0 != (_flags & (1<<6)))
|
||||
_state = HeaderState.CRC1;
|
||||
else
|
||||
_state = HeaderState.DONE;
|
||||
break;
|
||||
|
||||
case EHDATA:
|
||||
// ignore
|
||||
if (--_extHdrToRead <= 0) {
|
||||
if (0 != (_flags & (1<<4)))
|
||||
_state = HeaderState.NAME;
|
||||
if (0 != (_flags & (1<<3)))
|
||||
_state = HeaderState.COMMENT;
|
||||
else if (0 != (_flags & (1<<6)))
|
||||
_state = HeaderState.CRC1;
|
||||
else
|
||||
_state = HeaderState.DONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAME:
|
||||
// ignore
|
||||
if (c == 0) {
|
||||
if (0 != (_flags & (1<<3)))
|
||||
_state = HeaderState.COMMENT;
|
||||
else if (0 != (_flags & (1<<6)))
|
||||
_state = HeaderState.CRC1;
|
||||
else
|
||||
_state = HeaderState.DONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case COMMENT:
|
||||
// ignore
|
||||
if (c == 0) {
|
||||
if (0 != (_flags & (1<<6)))
|
||||
_state = HeaderState.CRC1;
|
||||
else
|
||||
_state = HeaderState.DONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case CRC1:
|
||||
// ignore
|
||||
_state = HeaderState.CRC2;
|
||||
break;
|
||||
|
||||
case CRC2:
|
||||
// ignore
|
||||
_state = HeaderState.DONE;
|
||||
break;
|
||||
|
||||
case DONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String args[]) {
|
||||
java.util.Random r = new java.util.Random();
|
||||
for (int i = 0; i < 1050; i++) {
|
||||
byte[] b = new byte[i];
|
||||
r.nextBytes(b);
|
||||
if (!test(b)) return;
|
||||
}
|
||||
for (int i = 1; i < 64*1024; i+= 29) {
|
||||
byte[] b = new byte[i];
|
||||
r.nextBytes(b);
|
||||
if (!test(b)) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean test(byte[] b) {
|
||||
int size = b.length;
|
||||
try {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(size);
|
||||
java.util.zip.GZIPOutputStream o = new java.util.zip.GZIPOutputStream(baos);
|
||||
o.write(b);
|
||||
o.finish();
|
||||
o.flush();
|
||||
byte compressed[] = baos.toByteArray();
|
||||
|
||||
java.io.ByteArrayOutputStream baos2 = new java.io.ByteArrayOutputStream(size);
|
||||
GunzipOutputStream out = new GunzipOutputStream(baos2);
|
||||
out.write(compressed);
|
||||
byte rv[] = baos2.toByteArray();
|
||||
if (rv.length != b.length)
|
||||
throw new RuntimeException("read length: " + rv.length + " expected: " + b.length);
|
||||
|
||||
if (!net.i2p.data.DataHelper.eq(rv, 0, b, 0, b.length)) {
|
||||
throw new RuntimeException("foo, read=" + rv.length);
|
||||
} else {
|
||||
System.out.println("match, w00t @ " + size);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error dealing with size=" + size + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
@@ -10,20 +10,14 @@ package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.BigPipedInputStream;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.ReusableGZIPInputStream;
|
||||
|
||||
/**
|
||||
* This does the transparent gzip decompression on the client side.
|
||||
@@ -152,9 +146,11 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
for (int i = 0; i < _headerBuffer.getValid(); i++) {
|
||||
if (isNL(_headerBuffer.getData()[i])) {
|
||||
if (lastEnd == -1) {
|
||||
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
|
||||
responseLine = DataHelper.getUTF8(_headerBuffer.getData(), 0, i+1); // includes NL
|
||||
responseLine = filterResponseLine(responseLine);
|
||||
responseLine = (responseLine.trim() + "\r\n");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Response: " + responseLine.trim());
|
||||
out.write(responseLine.getBytes());
|
||||
} else {
|
||||
for (int j = lastEnd+1; j < i; j++) {
|
||||
@@ -163,12 +159,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
int valLen = i-(j+1);
|
||||
if ( (keyLen <= 0) || (valLen < 0) )
|
||||
throw new IOException("Invalid header @ " + j);
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String key = DataHelper.getUTF8(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String val = null;
|
||||
if (valLen == 0)
|
||||
val = "";
|
||||
else
|
||||
val = new String(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
val = DataHelper.getUTF8(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Response header [" + key + "] = [" + val + "]");
|
||||
@@ -197,9 +193,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
} else if ("set-cookie".equals(lcKey)) {
|
||||
String lcVal = val.toLowerCase(Locale.US);
|
||||
if (lcVal.contains("domain=b32.i2p") ||
|
||||
lcVal.contains("domain=.b32.i2p")) {
|
||||
// Strip privacy-damaging "supercookie" for b32.i2p
|
||||
// Let's presume the user agent ignores a cookie for "i2p"
|
||||
lcVal.contains("domain=.b32.i2p") ||
|
||||
lcVal.contains("domain=i2p") ||
|
||||
lcVal.contains("domain=.i2p")) {
|
||||
// Strip privacy-damaging "supercookies" for i2p and b32.i2p
|
||||
// See RFC 6265 and http://publicsuffix.org/
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Stripping \"" + key + ": " + val + "\" from response ");
|
||||
@@ -245,94 +242,19 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Closing " + out + " threaded?? " + shouldCompress(), new Exception("I did it"));
|
||||
synchronized(this) {
|
||||
// synch with changing out field below
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
//out.flush();
|
||||
PipedInputStream pi = BigPipedInputStream.getInstance();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
Runnable r = new Pusher(pi, out);
|
||||
out = po;
|
||||
// TODO we should be able to do this inline somehow
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
if (tcg != null) {
|
||||
// Run in the client thread pool, as there should be an unused thread
|
||||
// there after the accept().
|
||||
// Overridden in I2PTunnelHTTPServer, where it does not use the client pool.
|
||||
try {
|
||||
tcg.getClientExecutor().execute(r);
|
||||
} catch (RejectedExecutionException ree) {
|
||||
// shouldn't happen
|
||||
throw ree;
|
||||
}
|
||||
} else {
|
||||
// Fallback in case TCG.getInstance() is null, never instantiated
|
||||
// and we were not started by TCG.
|
||||
// Maybe a plugin loaded before TCG? Should be rare.
|
||||
Thread t = new I2PAppThread(r, "Pusher");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private class Pusher implements Runnable {
|
||||
private final InputStream _inRaw;
|
||||
private final OutputStream _out;
|
||||
|
||||
public Pusher(InputStream in, OutputStream out) {
|
||||
_inRaw = in;
|
||||
_out = out;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
ReusableGZIPInputStream _in = ReusableGZIPInputStream.acquire();
|
||||
long written = 0;
|
||||
ByteArray ba = null;
|
||||
try {
|
||||
// blocking
|
||||
_in.initialize(_inRaw);
|
||||
ba = _cache.acquire();
|
||||
byte buf[] = ba.getData();
|
||||
int read = -1;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read " + read + " and writing it to the browser/streams");
|
||||
_out.write(buf, 0, read);
|
||||
_out.flush();
|
||||
written += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Decompressed: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error decompressing: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded(), ioe);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.error("OOM in HTTP Decompressor", oom);
|
||||
} finally {
|
||||
if (_log.shouldLog(Log.INFO) && (_in != null))
|
||||
_log.info("After decompression, written=" + written +
|
||||
" read=" + _in.getTotalRead()
|
||||
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
|
||||
+ ", finished=" + _in.getFinished());
|
||||
if (ba != null)
|
||||
_cache.release(ba);
|
||||
if (_out != null) try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
if (_in != null) {
|
||||
double compressed = _in.getTotalRead();
|
||||
double expanded = _in.getTotalExpanded();
|
||||
ReusableGZIPInputStream.release(_in);
|
||||
if (compressed > 0 && expanded > 0) {
|
||||
// only update the stats if we did something
|
||||
double ratio = compressed/expanded;
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio));
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded);
|
||||
}
|
||||
}
|
||||
OutputStream po = new GunzipOutputStream(out);
|
||||
synchronized(this) {
|
||||
out = po;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -887,13 +886,13 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
String privateKeyFile = null;
|
||||
if (args.length >= 4)
|
||||
privateKeyFile = args[3];
|
||||
task = new I2PTunnelClient(portNum, args[1], l, ownDest, this, this, privateKeyFile);
|
||||
I2PTunnelClientBase task = new I2PTunnelClient(portNum, args[1], l, ownDest, this, this, privateKeyFile);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("clientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@@ -966,10 +965,10 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelHTTPClient(clientPort, l, ownDest, proxy, this, this);
|
||||
I2PTunnelClientBase task = new I2PTunnelHTTPClient(clientPort, l, ownDest, proxy, this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("httpclientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@@ -1035,10 +1034,10 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelConnectClient(_port, l, ownDest, proxy, this, this);
|
||||
I2PTunnelClientBase task = new I2PTunnelConnectClient(_port, l, ownDest, proxy, this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String msg = "Invalid I2PTunnel configuration to create a CONNECT client connecting to the router at " + host + ':'+ port +
|
||||
@@ -1097,13 +1096,13 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
String privateKeyFile = null;
|
||||
if (args.length >= 4)
|
||||
privateKeyFile = args[3];
|
||||
task = new I2PTunnelIRCClient(_port, args[1], l, ownDest, this, this, privateKeyFile);
|
||||
I2PTunnelClientBase task = new I2PTunnelIRCClient(_port, args[1], l, ownDest, this, this, privateKeyFile);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@@ -1160,7 +1159,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
if (args.length == 3)
|
||||
privateKeyFile = args[2];
|
||||
try {
|
||||
I2PTunnelTask task = new I2PSOCKSTunnel(_port, l, ownDest, this, this, privateKeyFile);
|
||||
I2PTunnelClientBase task = new I2PSOCKSTunnel(_port, l, ownDest, this, this, privateKeyFile);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@@ -1207,7 +1207,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
if (args.length == 3)
|
||||
privateKeyFile = args[2];
|
||||
try {
|
||||
I2PTunnelTask task = new I2PSOCKSIRCTunnel(_port, l, ownDest, this, this, privateKeyFile);
|
||||
I2PTunnelClientBase task = new I2PSOCKSIRCTunnel(_port, l, ownDest, this, this, privateKeyFile);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@@ -1326,25 +1327,30 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
private void runConfig(String args[], Logging l) {
|
||||
if (args.length >= 2) {
|
||||
if (args.length >= 1) {
|
||||
int i = 0;
|
||||
if (args[0].equals("-s")) {
|
||||
boolean ssl = args[0].equals("-s");
|
||||
if (ssl) {
|
||||
_clientOptions.setProperty("i2cp.SSL", "true");
|
||||
i++;
|
||||
} else {
|
||||
_clientOptions.remove("i2cp.SSL");
|
||||
}
|
||||
host = args[i++];
|
||||
listenHost = host;
|
||||
port = args[i];
|
||||
if (i < args.length) {
|
||||
host = args[i++];
|
||||
listenHost = host;
|
||||
}
|
||||
if (i < args.length)
|
||||
port = args[i];
|
||||
l.log("New setting: " + host + ' ' + port + (ssl ? " SSL" : " non-SSL"));
|
||||
notifyEvent("configResult", "ok");
|
||||
} else {
|
||||
boolean ssl = Boolean.parseBoolean(_clientOptions.getProperty("i2cp.SSL"));
|
||||
l.log("Usage:\n" +
|
||||
" config [-s] <i2phost> <i2pport>\n" +
|
||||
" sets the connection to the i2p router.\n" +
|
||||
"Current setting:\n" +
|
||||
" " + host + ' ' + port + (ssl ? " SSL" : ""));
|
||||
" config [-s] [<i2phost>] [<i2pport>]\n" +
|
||||
" Sets the address and port of the I2P router.\n" +
|
||||
" Use -s for SSL.\n" +
|
||||
"Current setting: " + host + ' ' + port + (ssl ? " SSL" : " non-SSL"));
|
||||
notifyEvent("configResult", "error");
|
||||
}
|
||||
}
|
||||
@@ -1510,7 +1516,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
if (tasks.isEmpty()) {
|
||||
System.exit(0);
|
||||
}
|
||||
l.log("There are running tasks. Try 'list'.");
|
||||
l.log("There are running tasks. Try 'list' or 'close all'.");
|
||||
//notifyEvent("quitResult", "error");
|
||||
}
|
||||
|
||||
@@ -1598,7 +1604,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
private void runRun(String args[], Logging l) {
|
||||
if (args.length == 1) {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new FileReader(args[0]));
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), "UTF-8"));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
runCommand(line, l);
|
||||
@@ -1670,7 +1676,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
if (!_clientOptions.containsKey("outbound.nickname"))
|
||||
_clientOptions.setProperty("outbound.nickname", "I2Ping");
|
||||
}
|
||||
I2PTunnelTask task = new I2Ping(l, ownDest, this, this);
|
||||
I2PTunnelClientBase task = new I2Ping(l, ownDest, this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("pingTaskId", Integer.valueOf(task.getId()));
|
||||
} else {
|
||||
@@ -1747,8 +1754,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
*/
|
||||
public void log(String s) {
|
||||
System.out.println(s);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + "Display: " + s);
|
||||
//if (_log.shouldLog(Log.INFO))
|
||||
// _log.info(getPrefix() + "Display: " + s);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1766,8 +1773,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
l.log("Generating new keys...");
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination d = client.createDestination(writeTo);
|
||||
l.log("Secret key saved.\n" +
|
||||
"Public key: " + d.toBase64());
|
||||
l.log("New destination: " + d.toBase32());
|
||||
writeTo.flush();
|
||||
writeTo.close();
|
||||
writePubKey(d, pubDest, l);
|
||||
@@ -1790,7 +1796,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.readBytes(readFrom);
|
||||
l.log("Public key: " + d.toBase64());
|
||||
l.log("Destination: " + d.toBase32());
|
||||
readFrom.close();
|
||||
writePubKey(d, pubDest, l);
|
||||
} catch (I2PException ex) {
|
||||
@@ -1805,7 +1811,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
* Deprecated - only used by CLI
|
||||
*
|
||||
* @param d Destination to write
|
||||
* @param o stream to write the destination to
|
||||
* @param o stream to write the destination to, or null for noop
|
||||
* @param l logger to send messages to
|
||||
*/
|
||||
private static void writePubKey(Destination d, OutputStream o, Logging l) throws I2PException, IOException {
|
||||
|
||||
@@ -32,6 +32,9 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @param destinations peers we target, comma- or space-separated. Since 0.9.9, each dest may be appended with :port
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
@@ -44,11 +47,6 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
tunnel, pkf);
|
||||
|
||||
_addrs = new ArrayList<I2PSocketAddress>(1);
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
dests = new ArrayList<Destination>(1);
|
||||
buildAddresses(destinations);
|
||||
|
||||
@@ -68,9 +66,6 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> " + destinations);
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openClientResult", "ok");
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@ import javax.net.ssl.SSLServerSocketFactory;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@@ -52,7 +55,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
protected boolean _ownDest;
|
||||
|
||||
protected Destination dest;
|
||||
private int localPort;
|
||||
private volatile int localPort;
|
||||
private final String _handlerName;
|
||||
|
||||
/**
|
||||
* Protected for I2Ping since 0.9.11. Not for use outside package.
|
||||
@@ -75,11 +79,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
private volatile ThreadPoolExecutor _executor;
|
||||
|
||||
/** this is ONLY for shared clients */
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
/**
|
||||
* Only destroy and replace a static shared client socket manager if it's been connected before
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private enum SocketManagerState { INIT, CONNECTED }
|
||||
private static SocketManagerState _socketManagerState = SocketManagerState.INIT;
|
||||
|
||||
public static final String PROP_USE_SSL = I2PTunnelServer.PROP_USE_SSL;
|
||||
|
||||
/**
|
||||
* This constructor always starts the tunnel (ignoring the i2cp.delayOpen option).
|
||||
* It is used to add a client to an existing socket manager.
|
||||
* This constructor is used to add a client to an existing socket manager.
|
||||
* <p/>
|
||||
* As of 0.9.21 this does NOT open the local socket. You MUST call
|
||||
* {@link #startRunning()} for that. The local socket will be opened
|
||||
* immediately (ignoring the <code>i2cp.delayOpen</code> option).
|
||||
*
|
||||
* @param localPort if 0, use any port, get actual port selected with getLocalPort()
|
||||
* @param sktMgr the existing socket manager
|
||||
@@ -91,41 +108,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
chained = true;
|
||||
sockMgr = sktMgr;
|
||||
_clientId = clientId;
|
||||
_handlerName = "chained";
|
||||
this.localPort = localPort;
|
||||
this.l = l;
|
||||
_ownDest = true; // == ! shared client
|
||||
_context = tunnel.getContext();
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
initStats();
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
|
||||
Thread t = new I2PAppThread(this, "Client " + tunnel.listenHost + ':' + localPort);
|
||||
t.start();
|
||||
open = true;
|
||||
synchronized (this) {
|
||||
while (!listenerReady && open) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (open && listenerReady) {
|
||||
l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort);
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Client error for " + tunnel.listenHost + ':' + localPort + ", check logs");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main constructor.
|
||||
* This may take a LONG time if building and starting a new manager.
|
||||
* <p/>
|
||||
* As of 0.9.21 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
* <p/>
|
||||
* (0.9.20 claimed to be fast, but due to a bug it DID connect the manager
|
||||
* to the router. It did NOT open the local socket however, so it was still
|
||||
* necessary to call startRunning() for that.)
|
||||
*
|
||||
* @param localPort if 0, use any port, get actual port selected with getLocalPort()
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@@ -139,7 +139,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
/**
|
||||
* Use this to build a client with a persistent private key.
|
||||
* This may take a LONG time if building and starting a new manager.
|
||||
* <p/>
|
||||
* As of 0.9.21 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
* <p/>
|
||||
* (0.9.20 claimed to be fast, but due to a bug it DID connect the manager
|
||||
* to the router. It did NOT open the local socket however, so it was still
|
||||
* necessary to call startRunning() for that.)
|
||||
*
|
||||
* @param localPort if 0, use any port, get actual port selected with getLocalPort()
|
||||
* @param pkf Path to the private key file, or null to generate a transient key
|
||||
@@ -155,13 +161,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.localPort = localPort;
|
||||
this.l = l;
|
||||
_ownDest = ownDest; // == ! shared client
|
||||
|
||||
_handlerName = handlerName;
|
||||
|
||||
_context = tunnel.getContext();
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
initStats();
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
|
||||
// normalize path so we can find it
|
||||
@@ -180,54 +183,19 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||
if (tunnel.getClientOptions().getProperty("i2p.streaming.answerPings") == null)
|
||||
tunnel.getClientOptions().setProperty("i2p.streaming.answerPings", "false");
|
||||
|
||||
boolean openNow = !Boolean.parseBoolean(tunnel.getClientOptions().getProperty("i2cp.delayOpen"));
|
||||
if (openNow) {
|
||||
while (sockMgr == null) {
|
||||
verifySocketManager();
|
||||
if (sockMgr == null) {
|
||||
_log.error("Unable to connect to router and build tunnels for " + handlerName);
|
||||
// FIXME there is a loop in buildSocketManager(), do we really need another one here?
|
||||
// no matter, buildSocketManager() now throws an IllegalArgumentException
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
// can't be null unless we limit the loop above
|
||||
//if (sockMgr == null) {
|
||||
// l.log("Invalid I2CP configuration");
|
||||
// throw new IllegalArgumentException("Socket manager could not be created");
|
||||
//}
|
||||
l.log("Tunnels ready for client: " + handlerName);
|
||||
|
||||
} // else delay creating session until createI2PSocket() is called
|
||||
|
||||
Thread t = new I2PAppThread(this);
|
||||
t.setName("Client " + _clientId);
|
||||
t.start();
|
||||
open = true;
|
||||
synchronized (this) {
|
||||
while (!listenerReady && open) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (open && listenerReady) {
|
||||
if (openNow)
|
||||
l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort);
|
||||
else
|
||||
l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort + ", delaying tunnel open until required");
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Client error for " + tunnel.listenHost + ':' + localPort + ", check logs");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initStats() {
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the manager if it doesn't exist, AND connect it to the router and
|
||||
* build tunnels.
|
||||
*
|
||||
* Sets the this.sockMgr field if it is null, or if we want a new one.
|
||||
* This may take a LONG time if building a new manager.
|
||||
*
|
||||
@@ -269,15 +237,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.sockMgr = getSocketManager();
|
||||
}
|
||||
}
|
||||
connectManager();
|
||||
}
|
||||
|
||||
/** this is ONLY for shared clients */
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
|
||||
/**
|
||||
* This is ONLY for shared clients.
|
||||
* This may take a LONG time if building a new manager.
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@@ -289,7 +255,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
/**
|
||||
* This is ONLY for shared clients.
|
||||
* This may take a LONG time if building a new manager.
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@@ -301,7 +268,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
/**
|
||||
* This is ONLY for shared clients.
|
||||
* This may take a LONG time if building a new manager.
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@@ -312,14 +280,19 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
Log _log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class);
|
||||
if (socketManager != null && !socketManager.isDestroyed()) {
|
||||
I2PSession s = socketManager.getSession();
|
||||
if (s.isClosed()) {
|
||||
if (s.isClosed() && _socketManagerState != SocketManagerState.INIT) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(tunnel.getClientOptions().getProperty("inbound.nickname") + ": Building a new socket manager since the old one closed [s=" + s + "]");
|
||||
tunnel.removeSession(s);
|
||||
// make sure the old one is closed
|
||||
socketManager.destroySocketManager();
|
||||
_socketManagerState = SocketManagerState.INIT;
|
||||
// We could be here a LONG time, holding the lock
|
||||
socketManager = buildSocketManager(tunnel, pkf);
|
||||
// FIXME may not be the right place for this
|
||||
I2PSession sub = addSubsession(tunnel);
|
||||
if (sub != null && _log.shouldLog(Log.WARN))
|
||||
_log.warn("Added subsession " + sub);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(tunnel.getClientOptions().getProperty("inbound.nickname") + ": Not building a new socket manager since the old one is open [s=" + s + "]");
|
||||
@@ -332,10 +305,41 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(tunnel.getClientOptions().getProperty("inbound.nickname") + ": Building a new socket manager since there is no other one");
|
||||
socketManager = buildSocketManager(tunnel, pkf);
|
||||
I2PSession sub = addSubsession(tunnel);
|
||||
if (sub != null && _log.shouldLog(Log.WARN))
|
||||
_log.warn("Added subsession " + sub);
|
||||
}
|
||||
return socketManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a subsession to a shared client if necessary.
|
||||
*
|
||||
* @since 0.9.20
|
||||
*/
|
||||
protected static synchronized I2PSession addSubsession(I2PTunnel tunnel) {
|
||||
I2PSession sess = socketManager.getSession();
|
||||
if (sess.getMyDestination().getSigType() == SigType.DSA_SHA1)
|
||||
return null;
|
||||
Properties props = new Properties();
|
||||
props.putAll(tunnel.getClientOptions());
|
||||
String name = props.getProperty("inbound.nickname");
|
||||
if (name != null)
|
||||
props.setProperty("inbound.nickname", name + " (DSA)");
|
||||
name = props.getProperty("outbound.nickname");
|
||||
if (name != null)
|
||||
props.setProperty("outbound.nickname", name + " (DSA)");
|
||||
props.setProperty(I2PClient.PROP_SIGTYPE, "DSA_SHA1");
|
||||
try {
|
||||
return socketManager.addSubsession(null, props);
|
||||
} catch (I2PSessionException ise) {
|
||||
Log log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class);
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Failed to add subssession", ise);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the shared client, so that on restart in android
|
||||
* we won't latch onto the old one
|
||||
@@ -347,7 +351,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
/**
|
||||
* This may take a LONG time.
|
||||
* For NON-SHARED clients (ownDest = true).
|
||||
*
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@@ -356,8 +363,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
protected I2PSocketManager buildSocketManager() {
|
||||
return buildSocketManager(getTunnel(), this.privKeyFile, this.l);
|
||||
}
|
||||
|
||||
/**
|
||||
* This may take a LONG time.
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@@ -371,7 +380,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private static final int MAX_RETRIES = 4;
|
||||
|
||||
/**
|
||||
* This may take a LONG time.
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @param pkf absolute path or null
|
||||
* @return non-null
|
||||
@@ -383,7 +393,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
/**
|
||||
* This may take a LONG time.
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router.
|
||||
* Call verifySocketManager() for that.
|
||||
*
|
||||
* @param pkf absolute path or null
|
||||
* @return non-null
|
||||
@@ -400,51 +411,30 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
try {
|
||||
portNum = Integer.parseInt(tunnel.port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
|
||||
throw new IllegalArgumentException("Invalid port specified [" + tunnel.port + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
I2PSocketManager sockManager = null;
|
||||
// FIXME: Can't stop a tunnel from the UI while it's in this loop (no session yet)
|
||||
int retries = 0;
|
||||
while (sockManager == null) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
if (pkf != null) {
|
||||
// Persistent client dest
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(pkf);
|
||||
sockManager = I2PSocketManagerFactory.createManager(fis, tunnel.host, portNum, props);
|
||||
} catch (IOException ioe) {
|
||||
if (log != null)
|
||||
log.log("Error opening key file " + ioe);
|
||||
_log.error("Error opening key file", ioe);
|
||||
throw new IllegalArgumentException("Error opening key file " + ioe);
|
||||
} finally {
|
||||
if (fis != null)
|
||||
try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
fis = new FileInputStream(pkf);
|
||||
sockManager = I2PSocketManagerFactory.createDisconnectedManager(fis, tunnel.host, portNum, props);
|
||||
} else {
|
||||
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
|
||||
}
|
||||
|
||||
if (sockManager == null) {
|
||||
// try to make this error sensible as it will happen... sadly we can't get to the listenPort, only the listenHost
|
||||
String msg = "Unable to connect to the router at " + tunnel.host + ':' + portNum +
|
||||
" and build tunnels for the client";
|
||||
if (++retries < MAX_RETRIES) {
|
||||
if (log != null)
|
||||
log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
_log.error(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
} else {
|
||||
if (log != null)
|
||||
log.log(msg + ", giving up");
|
||||
_log.log(Log.CRIT, msg + ", giving up");
|
||||
// not clear if callers can handle null
|
||||
//return null;
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
try { Thread.sleep(RETRY_DELAY); } catch (InterruptedException ie) {}
|
||||
sockManager = I2PSocketManagerFactory.createDisconnectedManager(null, tunnel.host, portNum, props);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
throw new IllegalArgumentException("Can't create socket manager", ise);
|
||||
} catch (IOException ioe) {
|
||||
if (log != null)
|
||||
log.log("Error opening key file " + ioe);
|
||||
_log.error("Error opening key file", ioe);
|
||||
throw new IllegalArgumentException("Error opening key file", ioe);
|
||||
} finally {
|
||||
if (fis != null)
|
||||
try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
sockManager.setName("Client");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -453,6 +443,53 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
return sockManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Warning, blocks while connecting to router and building tunnels;
|
||||
* This may take a LONG time.
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private void connectManager() {
|
||||
int retries = 0;
|
||||
while (sockMgr.getSession().isClosed()) {
|
||||
try {
|
||||
sockMgr.getSession().connect();
|
||||
synchronized(I2PTunnelClientBase.class) {
|
||||
if (sockMgr == socketManager)
|
||||
_socketManagerState = SocketManagerState.CONNECTED;
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
// shadows instance _log
|
||||
Log _log = getTunnel().getContext().logManager().getLog(I2PTunnelClientBase.class);
|
||||
Logging log = this.l;
|
||||
// try to make this error sensible as it will happen...
|
||||
String portNum = getTunnel().port;
|
||||
if (portNum == null)
|
||||
portNum = "7654";
|
||||
String msg;
|
||||
if (getTunnel().getContext().isRouterContext())
|
||||
msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum +
|
||||
" and build tunnels for the client";
|
||||
else
|
||||
msg = "Unable to build tunnels for the client";
|
||||
if (++retries < MAX_RETRIES) {
|
||||
if (log != null)
|
||||
log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
_log.error(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds", ise);
|
||||
} else {
|
||||
if (log != null)
|
||||
log.log(msg + ", giving up");
|
||||
_log.log(Log.CRIT, msg + ", giving up", ise);
|
||||
throw new IllegalArgumentException(msg, ise);
|
||||
}
|
||||
try { Thread.sleep(RETRY_DELAY); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
@@ -469,11 +506,71 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually start working on incoming connections. *Must* be
|
||||
* Actually open the local socket and start working on incoming connections. *Must* be
|
||||
* called by derived classes after initialization.
|
||||
*
|
||||
* (this wasn't actually true until 0.9.20)
|
||||
*
|
||||
* This will be fast if i2cp.delayOpen is true, but could take
|
||||
* a LONG TIME if it is false, as it connects to the router and builds tunnels.
|
||||
*
|
||||
* Extending classes must check the value of boolean open after calling
|
||||
* super.startRunning(), if false then something went wrong.
|
||||
*
|
||||
*/
|
||||
public void startRunning() {
|
||||
boolean openNow = !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty("i2cp.delayOpen"));
|
||||
if (openNow) {
|
||||
while (sockMgr == null) {
|
||||
verifySocketManager();
|
||||
if (sockMgr == null) {
|
||||
_log.error("Unable to connect to router and build tunnels for " + _handlerName);
|
||||
// FIXME there is a loop in buildSocketManager(), do we really need another one here?
|
||||
// no matter, buildSocketManager() now throws an IllegalArgumentException
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
l.log("Tunnels ready for client: " + _handlerName);
|
||||
}
|
||||
}
|
||||
// can't be null unless we limit the loop above
|
||||
//if (sockMgr == null) {
|
||||
// l.log("Invalid I2CP configuration");
|
||||
// throw new IllegalArgumentException("Socket manager could not be created");
|
||||
//}
|
||||
|
||||
} // else delay creating session until createI2PSocket() is called
|
||||
startup();
|
||||
}
|
||||
|
||||
private void startup() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("startup " + _clientId, new Exception("I did it"));
|
||||
// prevent JVM exit when running outside the router
|
||||
boolean isDaemon = getTunnel().getContext().isRouterContext();
|
||||
open = true;
|
||||
Thread t = new I2PAppThread(this, "I2PTunnel Client " + getTunnel().listenHost + ':' + localPort, isDaemon);
|
||||
t.start();
|
||||
synchronized (this) {
|
||||
while (!listenerReady && open) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (open && listenerReady) {
|
||||
boolean openNow = !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty("i2cp.delayOpen"));
|
||||
if (openNow || chained)
|
||||
l.log("Client ready, listening on " + getTunnel().listenHost + ':' + localPort);
|
||||
else
|
||||
l.log("Client ready, listening on " + getTunnel().listenHost + ':' + localPort + ", delaying tunnel open until required");
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Client error for " + getTunnel().listenHost + ':' + localPort + ", check logs");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
synchronized (startLock) {
|
||||
startRunning = true;
|
||||
startLock.notify();
|
||||
@@ -582,18 +679,21 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
/**
|
||||
* Non-final since 0.9.11.
|
||||
* Any overrides must set listenerReady = true.
|
||||
* open will be true before being called.
|
||||
* Any overrides must set listenerReady = true and then notifyAll() if setup is successful,
|
||||
* and must call close() and then notifyAll() on failure or termination.
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
InetAddress addr = getListenHost(l);
|
||||
if (addr == null) {
|
||||
open = false;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
return;
|
||||
InetAddress addr = getListenHost(l);
|
||||
if (addr == null) {
|
||||
close(true);
|
||||
open = false;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
boolean useSSL = Boolean.parseBoolean(opts.getProperty(PROP_USE_SSL));
|
||||
if (useSSL) {
|
||||
@@ -625,7 +725,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
// Notify constructor that port is ready
|
||||
synchronized (this) {
|
||||
listenerReady = true;
|
||||
notify();
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
// Wait until we are authorized to process data
|
||||
@@ -653,14 +753,15 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
manageConnection(s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (open) {
|
||||
_log.error("Error listening for connections on " + localPort, ex);
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
synchronized (sockLock) {
|
||||
mySockets.clear();
|
||||
}
|
||||
open = false;
|
||||
if (open) {
|
||||
_log.error("Error listening for connections on " + addr + " port " + localPort, ex);
|
||||
l.log("Error listening for connections on " + addr + " port " + localPort + ": " + ex);
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
close(true);
|
||||
}
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
@@ -696,13 +797,32 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* Blocking runner, used during the connection establishment
|
||||
*/
|
||||
private class BlockingRunner implements Runnable {
|
||||
private Socket _s;
|
||||
private final Socket _s;
|
||||
public BlockingRunner(Socket s) { _s = s; }
|
||||
public void run() {
|
||||
clientConnectionRun(_s);
|
||||
try {
|
||||
clientConnectionRun(_s);
|
||||
} catch (Throwable t) {
|
||||
// probably an IllegalArgumentException from
|
||||
// connecting to the router in a delay-open or
|
||||
// close-on-idle tunnel (in connectManager() above)
|
||||
_log.error("Uncaught error in i2ptunnel client", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that the tunnel can be reopened after this by calling startRunning().
|
||||
* This may not release all resources. In particular, the I2PSocketManager remains
|
||||
* and it may have timer threads that continue running.
|
||||
*
|
||||
* To release all resources permanently, call destroy().
|
||||
*
|
||||
* Does nothing if open is already false.
|
||||
* Sets open = false but does not notifyAll().
|
||||
*
|
||||
* @return success
|
||||
*/
|
||||
public boolean close(boolean forced) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("close() called: forced = " + forced + " open = " + open + " sockMgr = " + sockMgr);
|
||||
@@ -738,8 +858,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("error closing", ex);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("error closing", ex);
|
||||
return false;
|
||||
}
|
||||
//l.log("Client closed.");
|
||||
|
||||
@@ -63,6 +63,8 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
"HTTP/1.1 405 Bad Method\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: METHOD NOT ALLOWED</H1>"+
|
||||
"The request uses a bad protocol. "+
|
||||
@@ -72,11 +74,16 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
"HTTP/1.1 403 Access Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>";
|
||||
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
@@ -85,11 +92,6 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPS Proxy on " + tunnel.listenHost + ':' + localPort, tunnel);
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openConnectClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (wwwProxy != null) {
|
||||
StringTokenizer tok = new StringTokenizer(wwwProxy, ", ");
|
||||
while (tok.hasMoreTokens())
|
||||
@@ -97,8 +99,6 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
}
|
||||
|
||||
setName("HTTPS Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +124,8 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
@Override
|
||||
public void startRunning() {
|
||||
super.startRunning();
|
||||
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY, getLocalPort());
|
||||
if (open)
|
||||
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY, getLocalPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,6 +30,9 @@ public class I2PTunnelHTTPBidirProxy extends I2PTunnelHTTPClient implements Runn
|
||||
|
||||
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
@@ -38,8 +41,6 @@ public class I2PTunnelHTTPBidirProxy extends I2PTunnelHTTPClient implements Runn
|
||||
// proxyList = new ArrayList();
|
||||
|
||||
setName(getLocalPort() + " -> HTTPClient [NO PROXIES]");
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ public class I2PTunnelHTTPBidirServer extends I2PTunnelHTTPServer {
|
||||
bidir = true;
|
||||
|
||||
/* start the httpclient */
|
||||
task = new I2PTunnelHTTPBidirProxy(localPort, l, sockMgr, getTunnel(), getEventDispatcher(), __serverId);
|
||||
I2PTunnelClientBase client = new I2PTunnelHTTPBidirProxy(localPort, l, sockMgr, getTunnel(), getEventDispatcher(), __serverId);
|
||||
client.startRunning();
|
||||
task = client;
|
||||
sockMgr.setName("Server"); // TO-DO: Need to change this to "Bidir"!
|
||||
getTunnel().addSession(sockMgr.getSession());
|
||||
l.log("Ready!");
|
||||
|
||||
@@ -91,6 +91,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 403 Access Denied\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>" +
|
||||
"You attempted to connect to a non-I2P website or location.<BR>";
|
||||
@@ -112,6 +114,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 503 Service Unavailable\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>" +
|
||||
"Your request was for a site outside of I2P, but you have no " +
|
||||
@@ -121,6 +125,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 409 Conflict\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: Destination key conflict</H1>" +
|
||||
"The addresshelper link you followed specifies a different destination key " +
|
||||
@@ -136,6 +142,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 404 Not Found\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: Helper key not resolvable.</H1>" +
|
||||
"The helper key you put for i2paddresshelper= is not resolvable. " +
|
||||
@@ -146,6 +154,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 409 New Address\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>New Host Name with Address Helper</H1>" +
|
||||
"The address helper link you followed is for a new host name that is not in your address book. " +
|
||||
@@ -157,6 +167,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 403 Bad Protocol\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: NON-HTTP PROTOCOL</H1>" +
|
||||
"The request uses a bad protocol. " +
|
||||
@@ -166,6 +178,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 403 Bad URI\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: INVALID REQUEST URI</H1>" +
|
||||
"The request URI is invalid, and probably contains illegal characters. " +
|
||||
@@ -175,6 +189,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 403 Access Denied\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>" +
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>";
|
||||
@@ -183,6 +199,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"HTTP/1.1 403 SSL Rejected\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: SSL to I2P address rejected</H1>" +
|
||||
"SSL for to .i2p addresses denied by configuration." +
|
||||
@@ -192,6 +210,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
* This constructor always starts the tunnel (ignoring the i2cp.delayOpen option).
|
||||
* It is used to add a client to an existing socket manager.
|
||||
*
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @param sockMgr the existing socket manager
|
||||
*/
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) {
|
||||
@@ -200,12 +221,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// proxyList = new ArrayList();
|
||||
|
||||
setName("HTTP Proxy on " + getTunnel().listenHost + ':' + localPort);
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
@@ -216,10 +238,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
_proxyNonce = Long.toString(_context.random().nextLong());
|
||||
|
||||
//proxyList = new ArrayList(); // We won't use outside of i2p
|
||||
if(waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openHTTPClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if(wwwProxy != null) {
|
||||
StringTokenizer tok = new StringTokenizer(wwwProxy, ", ");
|
||||
@@ -229,9 +247,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
|
||||
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
@@ -296,9 +311,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*60*1000 });
|
||||
super.startRunning();
|
||||
this.isr = new InternalSocketRunner(this);
|
||||
this.isr.start();
|
||||
_context.portMapper().register(PortMapper.SVC_HTTP_PROXY, getLocalPort());
|
||||
if (open) {
|
||||
this.isr = new InternalSocketRunner(this);
|
||||
this.isr.start();
|
||||
int port = getLocalPort();
|
||||
_context.portMapper().register(PortMapper.SVC_HTTP_PROXY, port);
|
||||
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY, port);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,10 +325,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
*/
|
||||
@Override
|
||||
public boolean close(boolean forced) {
|
||||
int port = getLocalPort();
|
||||
int reg = _context.portMapper().getPort(PortMapper.SVC_HTTP_PROXY);
|
||||
if(reg == getLocalPort()) {
|
||||
if (reg == port) {
|
||||
_context.portMapper().unregister(PortMapper.SVC_HTTP_PROXY);
|
||||
}
|
||||
reg = _context.portMapper().getPort(PortMapper.SVC_HTTPS_PROXY);
|
||||
if (reg == port) {
|
||||
_context.portMapper().unregister(PortMapper.SVC_HTTPS_PROXY);
|
||||
}
|
||||
boolean rv = super.close(forced);
|
||||
if(this.isr != null) {
|
||||
this.isr.stopRunning();
|
||||
@@ -611,6 +635,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
_("i2paddresshelper cannot help you with a destination like that!") +
|
||||
"</p>").getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
// XXX: should closeSocket(s) be in a finally block?
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
@@ -725,7 +750,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
out.write(getErrorPage("localhost", ERR_LOCALHOST).getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
s.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
@@ -1153,6 +1177,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
try {
|
||||
out.write(("HTTP/1.1 301 Address Helper Accepted\r\n" +
|
||||
"Location: " + uri + "\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n").getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
@@ -1378,6 +1404,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
private final static String ERR_HELPER_DISABLED =
|
||||
"HTTP/1.1 403 Disabled\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"Address helpers disabled";
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
"HTTP/1.1 407 Proxy Authentication Required\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: ";
|
||||
// put the auth type and realm in between
|
||||
@@ -78,6 +80,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
"HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
|
||||
"Your request was for a site outside of I2P, but you have no "+
|
||||
@@ -87,6 +91,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
"HTTP/1.1 503 Service Unavailable\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: DESTINATION NOT FOUND</H1>" +
|
||||
"That I2P Destination was not found. Perhaps you pasted in the " +
|
||||
|
||||
@@ -33,6 +33,9 @@ public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
super(s, i2ps, slock, initialI2PData, null, sockList, onFail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call once!
|
||||
*/
|
||||
@Override
|
||||
protected OutputStream getSocketOut() throws IOException {
|
||||
OutputStream raw = super.getSocketOut();
|
||||
|
||||
@@ -71,11 +71,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
|
||||
private static final long START_INTERVAL = (60 * 1000) * 3;
|
||||
private static final int MAX_LINE_LENGTH = 8*1024;
|
||||
/** ridiculously long, just to prevent OOM DOS @since 0.7.13 */
|
||||
private static final int MAX_HEADERS = 60;
|
||||
/** Includes request, just to prevent OOM DOS @since 0.9.20 */
|
||||
private static final int MAX_TOTAL_HEADER_SIZE = 32*1024;
|
||||
|
||||
private long _startedOn = 0L;
|
||||
private ConnThrottler _postThrottler;
|
||||
|
||||
private final static byte[] ERR_UNAVAILABLE =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
private final static String ERR_UNAVAILABLE =
|
||||
"HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
@@ -84,11 +89,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
"<html><head><title>503 Service Unavailable</title></head>\n"+
|
||||
"<body><h2>503 Service Unavailable</h2>\n" +
|
||||
"<p>This I2P website is unavailable. It may be down or undergoing maintenance.</p>\n" +
|
||||
"</body></html>")
|
||||
.getBytes();
|
||||
"</body></html>";
|
||||
|
||||
private final static byte[] ERR_DENIED =
|
||||
("HTTP/1.1 403 Denied\r\n"+
|
||||
private final static String ERR_DENIED =
|
||||
"HTTP/1.1 403 Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
@@ -97,11 +101,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
"<html><head><title>403 Denied</title></head>\n"+
|
||||
"<body><h2>403 Denied</h2>\n" +
|
||||
"<p>Denied due to excessive requests. Please try again later.</p>\n" +
|
||||
"</body></html>")
|
||||
.getBytes();
|
||||
"</body></html>";
|
||||
|
||||
private final static byte[] ERR_INPROXY =
|
||||
("HTTP/1.1 403 Denied\r\n"+
|
||||
private final static String ERR_INPROXY =
|
||||
"HTTP/1.1 403 Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
@@ -110,8 +113,64 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
"<html><head><title>403 Denied</title></head>\n"+
|
||||
"<body><h2>403 Denied</h2>\n" +
|
||||
"<p>Inproxy access denied. You must run <a href=\"https://geti2p.net/\">I2P</a> to access this site.</p>\n" +
|
||||
"</body></html>")
|
||||
.getBytes();
|
||||
"</body></html>";
|
||||
|
||||
private final static String ERR_SSL =
|
||||
"HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head><title>503 Service Unavailable</title></head>\n"+
|
||||
"<body><h2>503 Service Unavailable</h2>\n" +
|
||||
"<p>This I2P website is not configured for SSL.</p>\n" +
|
||||
"</body></html>";
|
||||
|
||||
private final static String ERR_REQUEST_URI_TOO_LONG =
|
||||
"HTTP/1.1 414 Request URI too long\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head><title>414 Request URI Too Long</title></head>\n"+
|
||||
"<body><h2>414 Request URI too long</h2>\n" +
|
||||
"</body></html>";
|
||||
|
||||
private final static String ERR_HEADERS_TOO_LARGE =
|
||||
"HTTP/1.1 431 Request header fields too large\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head><title>431 Request Header Fields Too Large</title></head>\n"+
|
||||
"<body><h2>431 Request header fields too large</h2>\n" +
|
||||
"</body></html>";
|
||||
|
||||
private final static String ERR_REQUEST_TIMEOUT =
|
||||
"HTTP/1.1 408 Request timeout\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head><title>408 Request Timeout</title></head>\n"+
|
||||
"<body><h2>408 Request timeout</h2>\n" +
|
||||
"</body></html>";
|
||||
|
||||
private final static String ERR_BAD_REQUEST =
|
||||
"HTTP/1.1 400 Bad Request\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head><title>400 Bad Request</title></head>\n"+
|
||||
"<body><h2>400 Bad request</h2>\n" +
|
||||
"</body></html>";
|
||||
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
@@ -131,7 +190,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private void setupI2PTunnelHTTPServer(String spoofHost) {
|
||||
_spoofHost = (spoofHost != null && spoofHost.trim().length() > 0) ? spoofHost.trim() : null;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -208,13 +266,88 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
if (socket.getLocalPort() == 443) {
|
||||
if (getTunnel().getClientOptions().getProperty("targetForPort.443") == null) {
|
||||
try {
|
||||
socket.getOutputStream().write(ERR_SSL.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
Socket s = getSocket(socket.getPeerDestination().calculateHash(), 443);
|
||||
Runnable t = new I2PTunnelRunner(s, socket, slock, null, null,
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
_clientExecutor.execute(t);
|
||||
return;
|
||||
}
|
||||
|
||||
long afterAccept = getTunnel().getContext().clock().now();
|
||||
|
||||
// The headers _should_ be in the first packet, but
|
||||
// may not be, depending on the client-side options
|
||||
|
||||
StringBuilder command = new StringBuilder(128);
|
||||
Map<String, List<String>> headers = readHeaders(socket, null, command,
|
||||
CLIENT_SKIPHEADERS, getTunnel().getContext());
|
||||
Map<String, List<String>> headers;
|
||||
try {
|
||||
// catch specific exceptions thrown, to return a good
|
||||
// error to the client
|
||||
headers = readHeaders(socket, null, command,
|
||||
CLIENT_SKIPHEADERS, getTunnel().getContext());
|
||||
} catch (SocketTimeoutException ste) {
|
||||
try {
|
||||
socket.getOutputStream().write(ERR_REQUEST_TIMEOUT.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", ste);
|
||||
return;
|
||||
} catch (EOFException eofe) {
|
||||
try {
|
||||
socket.getOutputStream().write(ERR_BAD_REQUEST.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", eofe);
|
||||
return;
|
||||
} catch (LineTooLongException ltle) {
|
||||
try {
|
||||
socket.getOutputStream().write(ERR_HEADERS_TOO_LARGE.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", ltle);
|
||||
return;
|
||||
} catch (RequestTooLongException rtle) {
|
||||
try {
|
||||
socket.getOutputStream().write(ERR_REQUEST_URI_TOO_LONG.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", rtle);
|
||||
return;
|
||||
} catch (BadRequestException bre) {
|
||||
try {
|
||||
socket.getOutputStream().write(ERR_BAD_REQUEST.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", bre);
|
||||
return;
|
||||
}
|
||||
long afterHeaders = getTunnel().getContext().clock().now();
|
||||
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
@@ -239,7 +372,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
try {
|
||||
// Send a 403, so the user doesn't get an HTTP Proxy error message
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_INPROXY);
|
||||
socket.getOutputStream().write(ERR_INPROXY.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {}
|
||||
try {
|
||||
socket.close();
|
||||
@@ -256,7 +389,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
try {
|
||||
// Send a 403, so the user doesn't get an HTTP Proxy error message
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_DENIED);
|
||||
socket.getOutputStream().write(ERR_DENIED.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {}
|
||||
try {
|
||||
socket.close();
|
||||
@@ -341,7 +474,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
try {
|
||||
// Send a 503, so the user doesn't get an HTTP Proxy error message
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_UNAVAILABLE);
|
||||
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {}
|
||||
try {
|
||||
socket.close();
|
||||
@@ -362,7 +495,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
try {
|
||||
// Send a 503, so the user doesn't get an HTTP Proxy error message
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_UNAVAILABLE);
|
||||
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {}
|
||||
try {
|
||||
socket.close();
|
||||
@@ -453,7 +586,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
try {
|
||||
if (browserout == null)
|
||||
browserout = _browser.getOutputStream();
|
||||
browserout.write(ERR_UNAVAILABLE);
|
||||
browserout.write(ERR_UNAVAILABLE.getBytes("UTF-8"));
|
||||
} catch (IOException ioe) {}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -633,9 +766,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** ridiculously long, just to prevent OOM DOS @since 0.7.13 */
|
||||
private static final int MAX_HEADERS = 60;
|
||||
|
||||
/**
|
||||
* Add an entry to the multimap.
|
||||
*/
|
||||
@@ -680,13 +810,14 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
* @param socket if null, use in as InputStream
|
||||
* @param in if null, use socket.getInputStream() as InputStream
|
||||
* @param command out parameter, first line
|
||||
* @param command out parameter, first line
|
||||
* @throws SocketTimeoutException if timeout is reached before newline
|
||||
* @throws EOFException if EOF is reached before newline
|
||||
* @throws LineTooLongException if too long
|
||||
* @throws LineTooLongException if one header too long, or too many headers, or total size too big
|
||||
* @throws RequestTooLongException if too long
|
||||
* @throws BadRequestException on bad headers
|
||||
* @throws IOException on other errors in the underlying stream
|
||||
*/
|
||||
private static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command,
|
||||
static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command,
|
||||
String[] skipHeaders, I2PAppContext ctx) throws IOException {
|
||||
HashMap<String, List<String>> headers = new HashMap<String, List<String>>();
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
@@ -694,51 +825,49 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
// slowloris / darkloris
|
||||
long expire = ctx.clock().now() + TOTAL_HEADER_TIMEOUT;
|
||||
if (socket != null) {
|
||||
readLine(socket, command, HEADER_TIMEOUT);
|
||||
try {
|
||||
readLine(socket, command, HEADER_TIMEOUT);
|
||||
} catch (LineTooLongException ltle) {
|
||||
// convert for first line
|
||||
throw new RequestTooLongException("Request too long - max " + MAX_LINE_LENGTH);
|
||||
}
|
||||
} else {
|
||||
boolean ok = DataHelper.readLine(in, command);
|
||||
if (!ok)
|
||||
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
throw new EOFException("EOF reached before the end of the headers");
|
||||
}
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Read the http command [" + command.toString() + "]");
|
||||
|
||||
// FIXME we probably don't need or want this in the outgoing direction
|
||||
int trimmed = 0;
|
||||
if (command.length() > 0) {
|
||||
for (int i = 0; i < command.length(); i++) {
|
||||
if (command.charAt(i) == 0) {
|
||||
command = command.deleteCharAt(i);
|
||||
i--;
|
||||
trimmed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trimmed > 0)
|
||||
ctx.statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed);
|
||||
|
||||
int totalSize = command.length();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
if (++i > MAX_HEADERS)
|
||||
throw new IOException("Too many header lines - max " + MAX_HEADERS);
|
||||
if (++i > MAX_HEADERS) {
|
||||
throw new LineTooLongException("Too many header lines - max " + MAX_HEADERS);
|
||||
}
|
||||
buf.setLength(0);
|
||||
if (socket != null) {
|
||||
readLine(socket, buf, expire - ctx.clock().now());
|
||||
} else {
|
||||
boolean ok = DataHelper.readLine(in, buf);
|
||||
if (!ok)
|
||||
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
throw new BadRequestException("EOF reached before the end of the headers");
|
||||
}
|
||||
if ( (buf.length() == 0) ||
|
||||
((buf.charAt(0) == '\n') || (buf.charAt(0) == '\r')) ) {
|
||||
// end of headers reached
|
||||
return headers;
|
||||
} else {
|
||||
if (ctx.clock().now() >= expire)
|
||||
throw new IOException("Headers took too long [" + buf.toString() + "]");
|
||||
if (ctx.clock().now() > expire) {
|
||||
throw new SocketTimeoutException("Headers took too long");
|
||||
}
|
||||
int split = buf.indexOf(":");
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
if (split <= 0)
|
||||
throw new BadRequestException("Invalid HTTP header, missing colon");
|
||||
totalSize += buf.length();
|
||||
if (totalSize > MAX_TOTAL_HEADER_SIZE)
|
||||
throw new LineTooLongException("Req+headers too big");
|
||||
String name = buf.substring(0, split).trim();
|
||||
String value = null;
|
||||
if (buf.length() > split + 1)
|
||||
@@ -831,5 +960,23 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private static class RequestTooLongException extends IOException {
|
||||
public RequestTooLongException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private static class BadRequestException extends IOException {
|
||||
public BadRequestException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
|
||||
public static final String PROP_DCC = "i2ptunnel.ircclient.enableDCC";
|
||||
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @param destinations peers we target, comma- or space-separated. Since 0.9.9, each dest may be appended with :port
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
@@ -81,8 +84,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
|
||||
_dccEnabled = Boolean.parseBoolean(tunnel.getClientOptions().getProperty(PROP_DCC));
|
||||
// TODO add some prudent tunnel options (or is it too late?)
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openIRCClientResult", "ok");
|
||||
}
|
||||
|
||||
@@ -197,7 +198,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
|
||||
@Override
|
||||
public void startRunning() {
|
||||
super.startRunning();
|
||||
_context.portMapper().register(PortMapper.SVC_IRC, getLocalPort());
|
||||
if (open)
|
||||
_context.portMapper().register(PortMapper.SVC_IRC, getLocalPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -99,6 +99,7 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread {
|
||||
|
||||
/**
|
||||
* When was the last data for this runner sent or received?
|
||||
* As of 0.9.20, returns -1 always!
|
||||
*
|
||||
* @return date (ms since the epoch), or -1 if no data has been transferred yet
|
||||
* @deprecated unused
|
||||
@@ -107,9 +108,11 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread {
|
||||
return lastActivityOn;
|
||||
}
|
||||
|
||||
/****
|
||||
private void updateActivity() {
|
||||
lastActivityOn = Clock.getInstance().now();
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* When this runner started up transferring data
|
||||
@@ -284,26 +287,31 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread {
|
||||
try {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
|
||||
if (len > 0) updateActivity();
|
||||
if (len > 0) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
//updateActivity();
|
||||
}
|
||||
|
||||
if (in.available() == 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": " + len + " bytes flushed through " + (_toI2P ? "to " : "from ")
|
||||
+ "outproxy");
|
||||
try {
|
||||
Thread.sleep(I2PTunnel.PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
if (_toI2P) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (in.available() <= 0)
|
||||
out.flush();
|
||||
} else {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
if (in.available() <= 0)
|
||||
out.flush(); // make sure the data get though
|
||||
}
|
||||
}
|
||||
//out.flush(); // close() flushes
|
||||
|
||||
@@ -198,6 +198,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
|
||||
/**
|
||||
* When was the last data for this runner sent or received?
|
||||
* As of 0.9.20, returns -1 always!
|
||||
*
|
||||
* @return date (ms since the epoch), or -1 if no data has been transferred yet
|
||||
* @deprecated unused
|
||||
@@ -206,9 +207,11 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
return lastActivityOn;
|
||||
}
|
||||
|
||||
/****
|
||||
private void updateActivity() {
|
||||
lastActivityOn = Clock.getInstance().now();
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* When this runner started up transferring data
|
||||
@@ -448,28 +451,33 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
try {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
|
||||
if (len > 0) updateActivity();
|
||||
if (len > 0) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
//updateActivity();
|
||||
}
|
||||
|
||||
if (in.available() == 0) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Flushing after sending " + len + " bytes through");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": " + len + " bytes flushed through " + (_toI2P ? "to " : "from ")
|
||||
+ i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6));
|
||||
try {
|
||||
Thread.sleep(I2PTunnel.PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
+ to);
|
||||
if (_toI2P) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (in.available() <= 0)
|
||||
out.flush();
|
||||
} else {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
if (in.available() <= 0)
|
||||
out.flush(); // make sure the data get though
|
||||
}
|
||||
}
|
||||
//out.flush(); // close() flushes
|
||||
|
||||
@@ -570,7 +570,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
blockingHandle(_i2ps);
|
||||
try {
|
||||
blockingHandle(_i2ps);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Uncaught error in i2ptunnel server", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -67,7 +68,7 @@ public class I2Ping extends I2PTunnelClientBase {
|
||||
// Notify constructor that port is ready
|
||||
synchronized (this) {
|
||||
listenerReady = true;
|
||||
notify();
|
||||
notifyAll();
|
||||
}
|
||||
l.log("*** I2Ping results:");
|
||||
try {
|
||||
@@ -157,7 +158,7 @@ public class I2Ping extends I2PTunnelClientBase {
|
||||
}
|
||||
|
||||
if (hostListFile != null) {
|
||||
BufferedReader br = new BufferedReader(new FileReader(hostListFile));
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(hostListFile), "UTF-8"));
|
||||
String line;
|
||||
List<PingHandler> pingHandlers = new ArrayList<PingHandler>();
|
||||
int i = 0;
|
||||
|
||||
@@ -209,7 +209,7 @@ public class TunnelController implements Logging {
|
||||
if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD)
|
||||
return;
|
||||
}
|
||||
new I2PAppThread(new Runnable() { public void run() { startTunnel(); } }).start();
|
||||
new I2PAppThread(new Runnable() { public void run() { startTunnel(); } }, "Tunnel Starter " + getName()).start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -651,9 +651,9 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
// same default logic as in EditBean.getSigType()
|
||||
if (!isClient(type) ||
|
||||
((type.equals(TYPE_IRC_CLIENT) || type.equals(TYPE_STD_CLIENT) ||
|
||||
type.equals(TYPE_SOCKS_IRC) || type.equals(TYPE_STREAMR_CLIENT))
|
||||
&& !Boolean.valueOf(getSharedClient()))) {
|
||||
type.equals(TYPE_IRC_CLIENT) || type.equals(TYPE_STD_CLIENT) ||
|
||||
type.equals(TYPE_SOCKS_IRC) || type.equals(TYPE_STREAMR_CLIENT) ||
|
||||
(type.equals(TYPE_HTTP_CLIENT) && Boolean.valueOf(getSharedClient()))) {
|
||||
if (!_config.containsKey(OPT_SIG_TYPE))
|
||||
_config.setProperty(OPT_SIG_TYPE, PREFERRED_SIGTYPE.name());
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
|
||||
private final List<TunnelController> _controllers;
|
||||
private final ReadWriteLock _controllersLock;
|
||||
private boolean _controllersLoaded;
|
||||
private final Object _controllersLoadedLock = new Object();
|
||||
private final String _configFile;
|
||||
|
||||
private static final String REGISTERED_NAME = "i2ptunnel";
|
||||
@@ -85,7 +87,8 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
if (SystemVersion.isAndroid() || !ctx.isRouterContext()) {
|
||||
_instance = new TunnelControllerGroup(ctx, null, null);
|
||||
_instance.startup();
|
||||
if (!SystemVersion.isAndroid())
|
||||
_instance.startup();
|
||||
} // else wait for the router to start it
|
||||
}
|
||||
return _instance;
|
||||
@@ -99,6 +102,7 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
* @param mgr may be null
|
||||
* @param args one arg, the config file, if not absolute will be relative to the context's config dir,
|
||||
* if empty or null, the default is i2ptunnel.config
|
||||
* @throws IllegalArgumentException if too many args
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public TunnelControllerGroup(I2PAppContext context, ClientAppManager mgr, String[] args) {
|
||||
@@ -146,7 +150,20 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public void startup() {
|
||||
loadControllers(_configFile);
|
||||
try {
|
||||
loadControllers(_configFile);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (DEFAULT_CONFIG_FILE.equals(_configFile) && !_context.isRouterContext()) {
|
||||
// for i2ptunnel command line
|
||||
synchronized (_controllersLoadedLock) {
|
||||
_controllersLoaded = true;
|
||||
}
|
||||
_log.logAlways(Log.WARN, "Not in router context and no preconfigured tunnels");
|
||||
} else {
|
||||
throw iae;
|
||||
}
|
||||
}
|
||||
startControllers();
|
||||
if (_mgr != null)
|
||||
_mgr.register(this);
|
||||
// RouterAppManager registers its own shutdown hook
|
||||
@@ -236,22 +253,28 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load up all of the tunnels configured in the given file (but do not start
|
||||
* them)
|
||||
* Load up all of the tunnels configured in the given file.
|
||||
* Prior to 0.9.20, also started the tunnels.
|
||||
* As of 0.9.20, does not start the tunnels, you must call startup()
|
||||
* or getInstance() instead of loadControllers().
|
||||
*
|
||||
* DEPRECATED for use outside this class. Use startup() or getInstance().
|
||||
*
|
||||
* @throws IllegalArgumentException if unable to load from file
|
||||
*/
|
||||
public synchronized void loadControllers(String configFile) {
|
||||
changeState(STARTING);
|
||||
synchronized (_controllersLoadedLock) {
|
||||
if (_controllersLoaded)
|
||||
return;
|
||||
}
|
||||
|
||||
Properties cfg = loadConfig(configFile);
|
||||
int i = 0;
|
||||
_controllersLock.writeLock().lock();
|
||||
try {
|
||||
while (true) {
|
||||
String type = cfg.getProperty("tunnel." + i + ".type");
|
||||
if (type == null)
|
||||
if (type == null)
|
||||
break;
|
||||
TunnelController controller = new TunnelController(cfg, "tunnel." + i + ".");
|
||||
_controllers.add(controller);
|
||||
@@ -260,11 +283,26 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
} finally {
|
||||
_controllersLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
synchronized (_controllersLoadedLock) {
|
||||
_controllersLoaded = true;
|
||||
}
|
||||
if (i > 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(i + " controllers loaded from " + configFile);
|
||||
} else {
|
||||
_log.logAlways(Log.WARN, "No i2ptunnel configurations found in " + configFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start all of the tunnels. Must call loadControllers() first.
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private synchronized void startControllers() {
|
||||
changeState(STARTING);
|
||||
I2PAppThread startupThread = new I2PAppThread(new StartControllers(), "Startup tunnels");
|
||||
startupThread.start();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(i + " controllers loaded from " + configFile);
|
||||
changeState(RUNNING);
|
||||
}
|
||||
|
||||
@@ -273,10 +311,14 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
synchronized(TunnelControllerGroup.this) {
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
if (_controllers.size() <= 0) {
|
||||
_log.logAlways(Log.WARN, "No configured tunnels to start");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
if (controller.getStartOnLoad())
|
||||
controller.startTunnel();
|
||||
controller.startTunnelBackground();
|
||||
}
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
@@ -294,6 +336,7 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
public synchronized void reloadControllers() {
|
||||
unloadControllers();
|
||||
loadControllers(_configFile);
|
||||
startControllers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,6 +345,11 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*
|
||||
*/
|
||||
public synchronized void unloadControllers() {
|
||||
synchronized (_controllersLoadedLock) {
|
||||
if (!_controllersLoaded)
|
||||
return;
|
||||
}
|
||||
|
||||
_controllersLock.writeLock().lock();
|
||||
try {
|
||||
destroyAllControllers();
|
||||
@@ -309,6 +357,10 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
} finally {
|
||||
_controllersLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
synchronized (_controllersLoadedLock) {
|
||||
_controllersLoaded = false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("All controllers stopped and unloaded");
|
||||
}
|
||||
@@ -511,11 +563,20 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of tunnels known
|
||||
* Retrieve a list of tunnels known.
|
||||
*
|
||||
* Side effect: if the tunnels have not been loaded from config yet, they
|
||||
* will be.
|
||||
*
|
||||
* @return list of TunnelController objects
|
||||
* @throws IllegalArgumentException if unable to load config from file
|
||||
*/
|
||||
public List<TunnelController> getControllers() {
|
||||
synchronized (_controllersLoadedLock) {
|
||||
if (!_controllersLoaded)
|
||||
loadControllers(_configFile);
|
||||
}
|
||||
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
return new ArrayList<TunnelController>(_controllers);
|
||||
|
||||
@@ -109,6 +109,7 @@ public class DCCClientManager extends EventReceiver {
|
||||
I2PTunnelDCCClient cTunnel = new I2PTunnelDCCClient(b32, localPort, port, l, sockMgr,
|
||||
_dispatch, _tunnel, ++_id);
|
||||
cTunnel.attachEventDispatcher(this);
|
||||
cTunnel.startRunning();
|
||||
int lport = cTunnel.getLocalPort();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Opened client tunnel at port " + lport +
|
||||
|
||||
@@ -37,6 +37,9 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase {
|
||||
public static final String CONNECT_STOP_EVENT = "connectionStopped";
|
||||
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @param dest the target, presumably b32
|
||||
* @param localPort if 0, use any port, get actual port selected with getLocalPort()
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
@@ -51,8 +54,6 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase {
|
||||
_expires = tunnel.getContext().clock().now() + INBOUND_EXPIRE;
|
||||
|
||||
setName("DCC send -> " + dest + ':' + remotePort);
|
||||
|
||||
startRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,8 +47,8 @@ public class IrcInboundFilter implements Runnable {
|
||||
in = new BufferedReader(new InputStreamReader(remote.getInputStream(), "ISO-8859-1"));
|
||||
output=local.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IrcInboundFilter: no streams",e);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IrcInboundFilter: no streams",e);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
|
||||
@@ -47,8 +47,8 @@ public class IrcOutboundFilter implements Runnable {
|
||||
in = new BufferedReader(new InputStreamReader(local.getInputStream(), "ISO-8859-1"));
|
||||
output=remote.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IrcOutboundFilter: no streams",e);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IrcOutboundFilter: no streams",e);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
|
||||
@@ -36,15 +36,28 @@ public abstract class LocalHTTPServer {
|
||||
private final static String ERR_404 =
|
||||
"HTTP/1.1 404 Not Found\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"HTTP Proxy local file not found";
|
||||
|
||||
private final static String ERR_ADD =
|
||||
"HTTP/1.1 409 Bad\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"Add to addressbook failed - bad parameters";
|
||||
|
||||
private final static String OK =
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Cache-Control: max-age=86400\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"I2P HTTP proxy OK";
|
||||
|
||||
/**
|
||||
* Very simple web server.
|
||||
*
|
||||
@@ -72,7 +85,7 @@ public abstract class LocalHTTPServer {
|
||||
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
||||
// a home page message for the curious...
|
||||
if (targetRequest.equals("/")) {
|
||||
out.write(("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: max-age=86400\r\n\r\nI2P HTTP proxy OK").getBytes("UTF-8"));
|
||||
out.write(OK.getBytes("UTF-8"));
|
||||
out.flush();
|
||||
return;
|
||||
}
|
||||
@@ -103,7 +116,7 @@ public abstract class LocalHTTPServer {
|
||||
else type = "text/html";
|
||||
out.write("HTTP/1.1 200 OK\r\nContent-Type: ".getBytes("UTF-8"));
|
||||
out.write(type.getBytes("UTF-8"));
|
||||
out.write("\r\nCache-Control: max-age=86400\r\n\r\n".getBytes("UTF-8"));
|
||||
out.write("\r\nCache-Control: max-age=86400\r\nConnection: close\r\nProxy-Connection: close\r\n\r\n".getBytes("UTF-8"));
|
||||
FileUtil.readFile(filename, themesDir.getAbsolutePath(), out);
|
||||
return;
|
||||
}
|
||||
@@ -181,6 +194,8 @@ public abstract class LocalHTTPServer {
|
||||
tbook = book;
|
||||
out.write(("HTTP/1.1 200 OK\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head>"+
|
||||
"<title>" + _("Redirecting to {0}", host) + "</title>\n" +
|
||||
|
||||
@@ -27,25 +27,22 @@ import net.i2p.util.Log;
|
||||
public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
|
||||
private HashMap<String, List<String>> proxies = null; // port# + "" or "default" -> hostname list
|
||||
protected Destination outProxyDest = null;
|
||||
|
||||
//public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest) {
|
||||
// I2PSOCKSTunnel(localPort, l, ownDest, (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
/** @param pkf private key file name or null for transient key */
|
||||
/**
|
||||
* As of 0.9.20 this is fast, and does NOT connect the manager to the router,
|
||||
* or open the local socket. You MUST call startRunning() for that.
|
||||
*
|
||||
* @param pkf private key file name or null for transient key
|
||||
*/
|
||||
public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel, String pkf) {
|
||||
super(localPort, ownDest, l, notifyThis, "SOCKS Proxy on " + tunnel.listenHost + ':' + localPort, tunnel, pkf);
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openSOCKSTunnelResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName("SOCKS Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
parseOptions();
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openSOCKSTunnelResult", "ok");
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ public class MultiSink<S extends Sink> implements Source, Sink {
|
||||
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sinks
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
Sink s = this.cache.get(from);
|
||||
if (s == null) {
|
||||
|
||||
@@ -23,6 +23,10 @@ public class ReplyTracker<S extends Sink> implements Source, Sink {
|
||||
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
this.cache.put(to, this.reply);
|
||||
this.sink.send(to, data);
|
||||
|
||||
@@ -24,6 +24,7 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
@@ -226,7 +227,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
}
|
||||
byte addr[] = new byte[addrLen];
|
||||
in.readFully(addr);
|
||||
connHostName = new String(addr);
|
||||
connHostName = DataHelper.getUTF8(addr);
|
||||
}
|
||||
_log.debug("DOMAINNAME address type in request: " + connHostName);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ public class SOCKSHeader {
|
||||
int namelen = (this.header[4] & 0xff);
|
||||
byte[] nameBytes = new byte[namelen];
|
||||
System.arraycopy(nameBytes, 0, this.header, 5, namelen);
|
||||
return new String(nameBytes);
|
||||
return DataHelper.getUTF8(nameBytes);
|
||||
}
|
||||
|
||||
public Destination getDestination() {
|
||||
|
||||
@@ -64,6 +64,10 @@ public class SOCKSUDPPort implements Source, Sink {
|
||||
this.udpsource.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
this.wrapper.send(from, data);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ public class SOCKSUDPUnwrapper implements Source, Sink {
|
||||
|
||||
/**
|
||||
*
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination ignored_from, byte[] data) {
|
||||
SOCKSHeader h;
|
||||
|
||||
@@ -25,6 +25,8 @@ public class SOCKSUDPWrapper implements Source, Sink {
|
||||
/**
|
||||
* Use the cached header, which should have the host string and port
|
||||
*
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
if (this.sink == null)
|
||||
|
||||
@@ -27,6 +27,10 @@ public class MultiSource implements Source, Sink {
|
||||
this.sinks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sinks
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination ignored_from, byte[] data) {
|
||||
for(Destination dest : this.sinks) {
|
||||
this.sink.send(dest, data);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -31,7 +33,9 @@ public class Pinger implements Source, Runnable {
|
||||
// send unsubscribe-message
|
||||
byte[] data = new byte[1];
|
||||
data[0] = 1;
|
||||
this.sink.send(null, data);
|
||||
try {
|
||||
this.sink.send(null, data);
|
||||
} catch (RuntimeException re) {}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -41,7 +45,14 @@ public class Pinger implements Source, Runnable {
|
||||
int i = 0;
|
||||
while(this.running) {
|
||||
//System.out.print("p");
|
||||
this.sink.send(null, data);
|
||||
try {
|
||||
this.sink.send(null, data);
|
||||
} catch (RuntimeException re) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (log.shouldWarn())
|
||||
log.warn("error sending", re);
|
||||
break;
|
||||
}
|
||||
synchronized(this.waitlock) {
|
||||
int delay = 10000;
|
||||
if (i < 5) {
|
||||
@@ -50,7 +61,9 @@ public class Pinger implements Source, Runnable {
|
||||
}
|
||||
try {
|
||||
this.waitlock.wait(delay);
|
||||
} catch(InterruptedException ie) {}
|
||||
} catch(InterruptedException ie) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package net.i2p.i2ptunnel.streamr;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* server-mode
|
||||
@@ -19,10 +21,18 @@ public class Subscriber implements Sink {
|
||||
this.subscriptions = new ConcurrentHashSet<Destination>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't really "send" anywhere, just subscribes or unsubscribes the destination
|
||||
*
|
||||
* @param dest to subscribe or unsubscribe
|
||||
* @param data must be a single byte, 0 to subscribe, 1 to unsubscribe
|
||||
*/
|
||||
public void send(Destination dest, byte[] data) {
|
||||
if(dest == null || data.length < 1) {
|
||||
// invalid packet
|
||||
// TODO: write to log
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (log.shouldWarn())
|
||||
log.warn("bad subscription from " + dest);
|
||||
} else {
|
||||
byte ctrl = data[0];
|
||||
if(ctrl == 0) {
|
||||
@@ -40,7 +50,9 @@ public class Subscriber implements Sink {
|
||||
multi.remove(dest);
|
||||
} else {
|
||||
// invalid packet
|
||||
// TODO: write to log
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (log.shouldWarn())
|
||||
log.warn("bad subscription from " + dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,10 @@ public class I2PSink implements Sink {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param src ignored */
|
||||
/**
|
||||
* @param src ignored
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public synchronized void send(Destination src, byte[] data) {
|
||||
//System.out.print("w");
|
||||
// create payload
|
||||
@@ -49,9 +52,8 @@ public class I2PSink implements Sink {
|
||||
this.sess.sendMessage(this.dest, payload,
|
||||
(this.raw ? I2PSession.PROTO_DATAGRAM_RAW : I2PSession.PROTO_DATAGRAM),
|
||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
} catch(I2PSessionException exc) {
|
||||
// TODO: handle better
|
||||
exc.printStackTrace();
|
||||
} catch (I2PSessionException ise) {
|
||||
throw new RuntimeException("failed to send data", ise);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ public class I2PSinkAnywhere implements Sink {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param to - where it's going */
|
||||
/**
|
||||
* @param to - where it's going
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public synchronized void send(Destination to, byte[] data) {
|
||||
// create payload
|
||||
byte[] payload;
|
||||
@@ -47,9 +50,8 @@ public class I2PSinkAnywhere implements Sink {
|
||||
this.sess.sendMessage(to, payload,
|
||||
(this.raw ? I2PSession.PROTO_DATAGRAM_RAW : I2PSession.PROTO_DATAGRAM),
|
||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
} catch(I2PSessionException exc) {
|
||||
// TODO: handle better
|
||||
exc.printStackTrace();
|
||||
} catch (I2PSessionException ise) {
|
||||
throw new RuntimeException("failed to send data", ise);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ package net.i2p.i2ptunnel.udp;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -48,7 +50,8 @@ public class I2PSource implements Source, Runnable {
|
||||
public void run() {
|
||||
// create dissector
|
||||
I2PDatagramDissector diss = new I2PDatagramDissector();
|
||||
while(true) {
|
||||
_running = true;
|
||||
while (_running) {
|
||||
try {
|
||||
// get id
|
||||
int id = this.queue.take();
|
||||
@@ -71,7 +74,10 @@ public class I2PSource implements Source, Runnable {
|
||||
}
|
||||
//System.out.print("r");
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (log.shouldWarn())
|
||||
log.warn("error sending", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,11 +97,15 @@ public class I2PSource implements Source, Runnable {
|
||||
}
|
||||
|
||||
public void disconnected(I2PSession arg0) {
|
||||
// ignore
|
||||
_running = false;
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
|
||||
// ignore
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
log.error(arg1, arg2);
|
||||
_running = false;
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,4 +116,5 @@ public class I2PSource implements Source, Runnable {
|
||||
protected final Thread thread;
|
||||
protected final boolean verify;
|
||||
protected final boolean raw;
|
||||
private volatile boolean _running;
|
||||
}
|
||||
|
||||
@@ -7,5 +7,9 @@ import net.i2p.data.Destination;
|
||||
* @author welterde
|
||||
*/
|
||||
public interface Sink {
|
||||
/**
|
||||
* @param src some implementations may ignore
|
||||
* @throws RuntimeException in some implementations
|
||||
*/
|
||||
public void send(Destination src, byte[] data);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
@@ -12,13 +13,15 @@ import net.i2p.data.Destination;
|
||||
*/
|
||||
public class UDPSink implements Sink {
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException on DatagramSocket IOException
|
||||
*/
|
||||
public UDPSink(InetAddress host, int port) {
|
||||
// create socket
|
||||
try {
|
||||
this.sock = new DatagramSocket();
|
||||
} catch(Exception e) {
|
||||
// TODO: fail better
|
||||
throw new RuntimeException("failed to open udp-socket", e);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("failed to open udp-socket", e);
|
||||
}
|
||||
|
||||
this.remoteHost = host;
|
||||
@@ -27,6 +30,10 @@ public class UDPSink implements Sink {
|
||||
this.remotePort = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param src ignored
|
||||
* @throws RuntimeException on DatagramSocket IOException
|
||||
*/
|
||||
public void send(Destination src, byte[] data) {
|
||||
// if data.length > this.sock.getSendBufferSize() ...
|
||||
|
||||
@@ -36,9 +43,8 @@ public class UDPSink implements Sink {
|
||||
// send packet
|
||||
try {
|
||||
this.sock.send(packet);
|
||||
} catch(Exception e) {
|
||||
// TODO: fail a bit better
|
||||
e.printStackTrace();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("failed to send data", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramPacket;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -12,11 +15,14 @@ import net.i2p.util.I2PAppThread;
|
||||
public class UDPSource implements Source, Runnable {
|
||||
public static final int MAX_SIZE = 15360;
|
||||
|
||||
/**
|
||||
* @throws RuntimeException on DatagramSocket IOException
|
||||
*/
|
||||
public UDPSource(int port) {
|
||||
// create udp-socket
|
||||
try {
|
||||
this.sock = new DatagramSocket(port);
|
||||
} catch(Exception e) {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to listen...", e);
|
||||
}
|
||||
|
||||
@@ -57,7 +63,9 @@ public class UDPSource implements Source, Runnable {
|
||||
this.sink.send(null, nbuf);
|
||||
//System.out.print("i");
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (log.shouldWarn())
|
||||
log.warn("error sending", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ import net.i2p.util.EventDispatcher;
|
||||
private static final AtomicLong __clientId = new AtomicLong();
|
||||
protected long _clientId;
|
||||
|
||||
protected Destination dest;
|
||||
|
||||
private final Object startLock = new Object();
|
||||
|
||||
private final I2PSession _session;
|
||||
@@ -98,6 +96,7 @@ import net.i2p.util.EventDispatcher;
|
||||
// create a session
|
||||
try {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(key);
|
||||
// FIXME this may not pick up non-default I2CP host/port settings from tunnel
|
||||
_session = client.createSession(in, tunnel.getClientOptions());
|
||||
connected(_session);
|
||||
} catch(Exception exc) {
|
||||
@@ -180,6 +179,7 @@ import net.i2p.util.EventDispatcher;
|
||||
*
|
||||
* @param to - ignored if configured for a single destination
|
||||
* (we use the dest specified in the constructor)
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
_i2pSink.send(to, data);
|
||||
|
||||
@@ -87,19 +87,12 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin
|
||||
|
||||
private void init(boolean verify, InputStream privData, String privkeyname, Logging l) {
|
||||
this.l = l;
|
||||
int portNum = 7654;
|
||||
if (getTunnel().port != null) {
|
||||
try {
|
||||
portNum = Integer.parseInt(getTunnel().port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
// create i2pclient
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
|
||||
try {
|
||||
// FIXME this may not pick up non-default I2CP host/port settings from tunnel
|
||||
_session = client.createSession(privData, getTunnel().getClientOptions());
|
||||
connected(_session);
|
||||
} catch(I2PSessionException exc) {
|
||||
@@ -195,6 +188,7 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin
|
||||
* Sink Methods
|
||||
*
|
||||
* @param to
|
||||
* @throws RuntimeException if session is closed
|
||||
*
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user