forked from I2P_Developers/i2p.i2p
Compare commits
657 Commits
i2p-0.9.17
...
i2p-0.9.21
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | ||
| dd99978b19 | |||
| dd265bbd54 | |||
|
|
f5ba1b1b97 | ||
| 7825f0f84f | |||
|
|
69a0324e86 | ||
|
|
957d3545b6 | ||
|
|
466348a8c5 | ||
| e5b7e97ff4 | |||
|
|
4613e5f847 | ||
|
|
44f8154f07 | ||
| 5486874d1a | |||
| d868ca4740 | |||
| 780479be4b | |||
| 4705f01bc5 | |||
| 2f5f91a084 | |||
|
|
e44fe98c7e | ||
|
|
d8fbc9c170 | ||
|
|
facbe8f9a0 | ||
|
|
4d8e577ffd | ||
|
|
80eb7635c1 | ||
|
|
e3103762b6 | ||
| cce710e377 | |||
|
|
013c79bc45 | ||
|
|
1e375886bd | ||
|
|
d1ac24c65d | ||
| 6aa1284848 | |||
| bb082c35fc | |||
| f7577e7de8 | |||
| b5df13d8b7 | |||
| 9d76790cc5 | |||
| 706ee243a5 | |||
| 351a1a8d27 | |||
| 6916cd7977 | |||
|
|
a444c25c2c | ||
|
|
45bc533e38 | ||
| 03e890b01c | |||
|
|
0c90162e20 | ||
|
|
ddc3ef8db3 | ||
| fcec43b7ca | |||
| edb614d970 | |||
|
|
820b99e3d3 | ||
|
|
cf0453cee0 | ||
| 75a8d8f6d3 | |||
| 1ac8d99145 | |||
|
|
b7b5512e7a | ||
| 485acd6c8d | |||
|
|
bb68728c82 | ||
|
|
f3b2eb69d2 | ||
|
|
168d688fc9 | ||
|
|
ade93ea76d | ||
|
|
44503af88b | ||
| eb7693561b | |||
| 3ccb03f9be | |||
| f3a2af8f10 | |||
| 2ef615a3f7 | |||
| 20197fc3ec | |||
| fadc624f7c | |||
| 22c4149358 | |||
| c770c6bc6a | |||
| 891408191e | |||
| 9a8fa246a9 | |||
| 83c3152b5d | |||
| 956730c5e9 | |||
| 72b9c92a6e | |||
| 349255d252 | |||
| ac902badcd | |||
| 9dc2ae0d7e | |||
| 188bd6db7b | |||
| 3a8ce64c84 | |||
| f3d573cab0 | |||
| 9e18c7ea18 | |||
| a975dc4427 | |||
| b875e284af | |||
|
|
46fe4298b9 | ||
|
|
9790d3ba64 | ||
|
|
2d31f30a22 | ||
|
|
2fefe93922 | ||
|
|
399b068a4e | ||
| dcffde6eeb | |||
| 78074f6a7e | |||
| 79dc01f7e4 | |||
| 0f6040ecb1 | |||
| a0ab72e362 | |||
| 2c45378c6d | |||
| 44c75187f5 | |||
| 2609a4d124 | |||
| 2d58501db3 | |||
| a337185820 | |||
| 9c0aa0c271 | |||
| b2e908f094 | |||
| ef32d37073 | |||
| f0961a9658 | |||
|
|
876b5714be | ||
|
|
825cd7ff4c | ||
|
|
47f3476078 | ||
|
|
7b10ebc117 | ||
|
|
e5cdfd206d | ||
| dd4c62b560 | |||
| aae801efaf | |||
| e02d44433d | |||
| 590a3c98e5 | |||
| 7f472e4ee9 | |||
| a3802d4d8b | |||
| 59348f8dbd | |||
| 8742a66f2f | |||
| a2f027e136 | |||
| cb4359cd0a | |||
| 163c172823 | |||
| 80a2d2c1f5 | |||
| 486f282999 | |||
| 1293dccf35 | |||
| 91fe62eee3 | |||
| d3f5596cb2 | |||
| d7a88db87a | |||
| 0af1f67c33 | |||
| 8dde7b70db | |||
| 64faeef6c4 | |||
| c79e4aeaea | |||
| 8b6a86e391 | |||
| 92daf4a8df | |||
| 819b07a52a | |||
| b8f8c6129d | |||
| 25d1ae195a | |||
| d22b05e114 | |||
| db25eff74a | |||
| c927441d66 | |||
| 7e4832d5f2 | |||
| 819b35c760 | |||
| 071498c413 | |||
| 7125ed0492 | |||
| de201bdd9c | |||
| 4fccd258e6 | |||
|
|
56d705739b | ||
|
|
2a9d61b1ed | ||
| a9f6839a04 | |||
| 5b555855ef | |||
| 76cf80a3d0 | |||
| 4c6aaa32b6 | |||
| 74ab1bff53 | |||
| b5bba5e3c8 | |||
|
|
7e5bd17714 | ||
| ec6207fc78 | |||
| 36d47a0ba9 | |||
| 0289cefd8d | |||
|
|
521eb2d8f8 | ||
| 0494266649 | |||
| 8fac5c064e | |||
|
|
0b6f74e646 | ||
|
|
b8b272a5b8 | ||
|
|
a570e09166 | ||
|
|
1919e36c30 | ||
|
|
812c00f11e | ||
|
|
419e27cfd1 | ||
|
|
d761c02909 | ||
| c7d1d2b69a | |||
| 0972b6b56a | |||
| 6e3cf7869f | |||
| f7337b4891 | |||
| 55161dec17 | |||
| b65b53b0df | |||
| 49e1e1c8a4 | |||
|
|
9b73fcda40 | ||
|
|
b92e1ee9aa | ||
|
|
04ac54cd35 | ||
|
|
d47916f753 | ||
| b0ea1d691a | |||
| ce041dfbc1 | |||
| 4613da093d | |||
|
|
2d5f7aaae5 | ||
|
|
5a7a7ac83d | ||
|
|
f217af2deb | ||
|
|
6d58f9a354 | ||
|
|
29953ea5e4 | ||
|
|
bb9cef1e40 | ||
|
|
ece2f1484c | ||
|
|
a3c8a4363d | ||
|
|
2f90b5a201 | ||
|
|
d4bbdc28f3 | ||
|
|
f41df969b7 | ||
|
|
f87d006a1c | ||
|
|
f4fa9a7d8f | ||
|
|
c52047e6cd | ||
|
|
9163d41228 | ||
|
|
1be9bb29e8 | ||
|
|
522a89a045 | ||
| 06b9b6a7fb | |||
| 7f9c565cd7 | |||
| 201afc823e | |||
| f4c79c885a | |||
| 656202c9db | |||
| 72b64072d5 | |||
| b72271f9a4 | |||
| b0d09d28f4 | |||
| b9197e35b5 | |||
| e431be2cbe | |||
| 761c883c1f | |||
| 60f86f342b | |||
| 1234b6b148 | |||
|
|
6f45242fc8 | ||
| 36c45ccb7b | |||
| 90cf71b5bc | |||
| 7165dc7860 | |||
| 5491287931 | |||
| 7256096b8a | |||
| b6008b5414 | |||
| 1042d21278 | |||
| 47eff7ee86 | |||
| 3da850a6b9 | |||
| a1358deda2 | |||
| df0bbfd615 | |||
| 0568ac3aa5 | |||
| e63a69170e | |||
| 711f8dedd9 | |||
| 09c3737a94 | |||
| 9384424173 | |||
| 4936f08212 | |||
| 03f4ebbe35 | |||
| 0671785ab2 | |||
| 381dbc4b4a | |||
| 84cf531f5f | |||
| 175806115b | |||
| 86b45ab1e5 | |||
| 17939036bc | |||
| 5bf515441e | |||
| 06edb9f2a6 | |||
| 33b58f5fab | |||
| 47a012a4dd | |||
| e5801be43e | |||
| d5a6ac591c | |||
| 59373f9bdf | |||
| 5da492b9e5 | |||
| 9bcc951f10 | |||
| 3270ba840e | |||
| 45b3e44cc2 | |||
| 7ed855b2d2 | |||
| f08552c2d1 | |||
| 9a4c19b24b | |||
| 690b695373 | |||
| 65348b2365 | |||
| 285c13d900 | |||
| 0a938d9048 | |||
| a02a265802 | |||
| eeeeef81cf | |||
| bcb9fe5f24 | |||
| 37f34d83f8 | |||
| b3238079c3 | |||
| ee1edb3383 | |||
| 7767430af2 | |||
| 2e5185aa99 | |||
| 6e847a4cc4 | |||
| 045f6dccf8 | |||
| d7895a456a | |||
| 7753d05b61 | |||
| 043b4776c3 | |||
| 5db764de5f | |||
| 3ae846a713 | |||
| 927e29b8ef | |||
|
|
d271411552 | ||
|
|
31d98ac4a5 | ||
|
|
78f4cc8e30 | ||
|
|
cce30a8f42 | ||
| a9e928fb46 | |||
| 60017f7c55 | |||
| eb46f74e24 | |||
|
|
5e890bd781 | ||
| 20facf78d0 | |||
| 96db43cc8e | |||
| ab4f209c10 | |||
| fa51a0aef4 | |||
| aa6a5e053c | |||
| 03df6c2ba0 | |||
|
|
501f645e60 | ||
| 23534b31c6 | |||
| d35363cdbc | |||
| ba34c90b7f | |||
|
|
94a19171ed | ||
|
|
8099591589 | ||
|
|
df6bbc59b3 | ||
| 05a616aa0d | |||
| c84105e783 | |||
| 262721cc90 | |||
|
|
c24168d5cd | ||
| 4e529a68d3 | |||
|
|
4f3244e93b | ||
| b2e17916e4 | |||
| 57ac344e7f | |||
| 98e275d908 | |||
| 8420b6c715 | |||
|
|
3dfcb2d5cc | ||
| 540720a912 | |||
| f86200e3ae | |||
| 9e43618028 | |||
| aacdba1bc7 | |||
| c28d060d52 | |||
| bf3fdbb1ab | |||
| 0a7a637d46 | |||
| e842165265 | |||
| 2db82da910 | |||
| 9953bc3024 | |||
| 2ba4992d88 | |||
| 5e67008d26 | |||
| e7b50c5940 | |||
| 78d7277298 | |||
| fb641187b8 | |||
| 4b2715c36f | |||
| f1e9f5d4fd | |||
| 2d43d349ab | |||
| 1773fc0e0d | |||
| 6d6f7fb89b | |||
| 449ce3176e | |||
| 5383f9f097 | |||
| a16d17c422 | |||
| 31cc0764a9 | |||
| 8f8adfa39e | |||
| 5044f3e58f |
50
.tx/config
50
.tx/config
@@ -33,6 +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
|
||||
;; 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
|
||||
@@ -81,11 +83,15 @@ source_lang = en
|
||||
trans.ar = apps/routerconsole/locale-news/messages_ar.po
|
||||
trans.de = apps/routerconsole/locale-news/messages_de.po
|
||||
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
|
||||
;; 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
|
||||
trans.mg = apps/routerconsole/locale-news/messages_mg.po
|
||||
trans.nb = apps/routerconsole/locale-news/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-news/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-news/messages_pl.po
|
||||
@@ -94,6 +100,7 @@ trans.pt_BR = apps/routerconsole/locale-news/messages_pt_BR.po
|
||||
trans.ro = apps/routerconsole/locale-news/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
|
||||
trans.sk = apps/routerconsole/locale-news/messages_sk.po
|
||||
trans.sq = apps/routerconsole/locale-news/messages_sq.po
|
||||
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
|
||||
trans.tr_TR = apps/routerconsole/locale-news/messages_tr.po
|
||||
trans.uk_UA = apps/routerconsole/locale-news/messages_uk.po
|
||||
@@ -114,6 +121,7 @@ trans.fr = apps/routerconsole/locale-countries/messages_fr.po
|
||||
trans.hu = apps/routerconsole/locale-countries/messages_hu.po
|
||||
trans.it = apps/routerconsole/locale-countries/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-countries/messages_ja.po
|
||||
trans.mg = apps/routerconsole/locale-countries/messages_mg.po
|
||||
trans.nb = apps/routerconsole/locale-countries/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-countries/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-countries/messages_pl.po
|
||||
@@ -208,17 +216,22 @@ trans.cs = apps/susimail/locale/messages_cs.po
|
||||
trans.da = apps/susimail/locale/messages_da.po
|
||||
trans.de = apps/susimail/locale/messages_de.po
|
||||
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
|
||||
;; 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
|
||||
trans.nl = apps/susimail/locale/messages_nl.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.pl = apps/susimail/locale/messages_pl.po
|
||||
trans.pt = apps/susimail/locale/messages_pt.po
|
||||
trans.pt_BR = apps/susimail/locale/messages_pt_BR.po
|
||||
trans.ro = apps/susimail/locale/messages_ro.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sq = apps/susimail/locale/messages_sq.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.uk_UA = apps/susimail/locale/messages_uk.po
|
||||
trans.vi = apps/susimail/locale/messages_vi.po
|
||||
trans.zh_CN = apps/susimail/locale/messages_zh.po
|
||||
@@ -230,16 +243,21 @@ trans.cs = debian/po/cs.po
|
||||
trans.de = debian/po/de.po
|
||||
trans.el = debian/po/el.po
|
||||
trans.es = debian/po/es.po
|
||||
trans.fi = debian/po/fi.po
|
||||
trans.fr = debian/po/fr.po
|
||||
trans.id = debian/po/id.po
|
||||
trans.it = debian/po/it.po
|
||||
trans.hu = debian/po/hu.po
|
||||
trans.ja = debian/po/ja.po
|
||||
trans.ko = debian/po/ko.po
|
||||
trans.nl = debian/po/nl.po
|
||||
trans.pl = debian/po/pl.po
|
||||
trans.pt = debian/po/pt.po
|
||||
trans.pt_BR = debian/po/pt_BR.po
|
||||
trans.ro = debian/po/ro.po
|
||||
trans.ru_RU = debian/po/ru.po
|
||||
trans.sk = debian/po/sk.po
|
||||
trans.sq = debian/po/sq.po
|
||||
trans.sv_SE = debian/po/sv.po
|
||||
trans.uk_UA = debian/po/uk.po
|
||||
trans.tr_TR = debian/po/tr.po
|
||||
@@ -248,12 +266,20 @@ trans.zh_CN = debian/po/zh.po
|
||||
[I2P.i2prouter-script]
|
||||
source_file = installer/resources/locale/po/messages_en.po
|
||||
source_lang = en
|
||||
;; currently fails check
|
||||
;;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
|
||||
trans.pl = installer/resources/locale/po/messages_pl.po
|
||||
trans.ja = installer/resources/locale/po/messages_ja.po
|
||||
;; currently fails check
|
||||
;;trans.ko = installer/resources/locale/po/messages_ko.po
|
||||
trans.nl = installer/resources/locale/po/messages_nl.po
|
||||
trans.pl = installer/resources/locale/po/messages_pl.po
|
||||
trans.pt = installer/resources/locale/po/messages_pt.po
|
||||
trans.pt_BR = installer/resources/locale/po/messages_pt_BR.po
|
||||
@@ -262,6 +288,8 @@ trans.ru_RU = installer/resources/locale/po/messages_ru.po
|
||||
trans.sk = installer/resources/locale/po/messages_sk.po
|
||||
trans.sv_SE = installer/resources/locale/po/messages_sv.po
|
||||
trans.tr_TR = installer/resources/locale/po/messages_tr.po
|
||||
;; currently fails check
|
||||
;;trans.uk_UA = installer/resources/locale/po/messages_uk.po
|
||||
trans.zh_CN = installer/resources/locale/po/messages_zh.po
|
||||
|
||||
[I2P.getopt]
|
||||
@@ -271,28 +299,44 @@ 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
|
||||
;; 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
|
||||
trans.nl = core/java/src/gnu/getopt/MessagesBundle_nl.properties
|
||||
trans.nb = core/java/src/gnu/getopt/MessagesBundle_nb.properties
|
||||
trans.pl = core/java/src/gnu/getopt/MessagesBundle_pl.properties
|
||||
trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.pt = core/java/src/gnu/getopt/MessagesBundle_pt.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
trans.ro = core/java/src/gnu/getopt/MessagesBundle_ro.properties
|
||||
trans.ru_RU = core/java/src/gnu/getopt/MessagesBundle_ru.properties
|
||||
trans.sk = core/java/src/gnu/getopt/MessagesBundle_sk.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.sq = core/java/src/gnu/getopt/MessagesBundle_sq.properties
|
||||
trans.uk_UA = core/java/src/gnu/getopt/MessagesBundle_uk.properties
|
||||
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
|
||||
;; 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.41:
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,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 {
|
||||
|
||||
11
apps/addressbook/java/src/net/i2p/addressbook/package.html
Normal file
11
apps/addressbook/java/src/net/i2p/addressbook/package.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The addressbook application, which fetches hosts.txt files from subscription URLS via
|
||||
HTTP and adds new hosts to the local database.
|
||||
While implemented as a webapp, this application contains no user interface.
|
||||
May also be packaged as a jar, as is done for Android.
|
||||
The webapp named 'addressbook' in the console is actually SusiDNS.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
|
||||
99
apps/apparmor/home.i2p.i2prouter
Normal file
99
apps/apparmor/home.i2p.i2prouter
Normal file
@@ -0,0 +1,99 @@
|
||||
# Last Modified: Sun Apr 12 22:08:32 2015
|
||||
# vim:syntax=apparmor et ts=8 sw=4
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
$INSTALL_PATH/{i2prouter,runplain.sh} flags=(complain) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/fonts>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/ssl_certs>
|
||||
|
||||
capability sys_ptrace,
|
||||
network inet stream,
|
||||
network inet6 stream,
|
||||
|
||||
$INSTALL_PATH/ r,
|
||||
$INSTALL_PATH/{i2psvc,wrapper} rmix,
|
||||
owner $INSTALL_PATH/** rwklm,
|
||||
|
||||
# Needed for Java
|
||||
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,
|
||||
/usr/share/javazi/** r,
|
||||
|
||||
# Debian
|
||||
/etc/java-{6,7,8}-openjdk/** r,
|
||||
/usr/lib/jvm/default-java/jre/bin/java rix,
|
||||
|
||||
# Debian, Ubuntu, openSUSE
|
||||
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/java rix,
|
||||
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/keytool rix,
|
||||
|
||||
# Raspbian
|
||||
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/java rix,
|
||||
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/keytool rix,
|
||||
|
||||
|
||||
# Fonts are needed for I2P's graphs
|
||||
/usr/share/java/java-atk-wrapper.jar r,
|
||||
|
||||
# Used by some plugins
|
||||
/usr/share/java/eclipse-ecj-*.jar r,
|
||||
|
||||
/{,var/}tmp/ rwm,
|
||||
owner /{,var/}tmp/** rwklm,
|
||||
|
||||
/{,usr/}bin/{,b,d}ash rix,
|
||||
/{,usr/}bin/cat rix,
|
||||
/{,usr/}bin/cut rix,
|
||||
/{,usr/}bin/dirname rix,
|
||||
/{,usr/}bin/expr rix,
|
||||
/{,usr/}bin/{,g,m}awk rix,
|
||||
/{,usr/}bin/grep rix,
|
||||
/{,usr/}bin/id rix,
|
||||
/{,usr/}bin/ldd rix,
|
||||
/{,usr/}bin/ls rix,
|
||||
/{,usr/}bin/mkdir rix,
|
||||
/{,usr/}bin/nohup rix,
|
||||
/{,usr/}bin/ps rix,
|
||||
/{,usr/}bin/rm rix,
|
||||
/{,usr/}bin/sed rix,
|
||||
/{,usr/}bin/sleep rix,
|
||||
/{,usr/}bin/tail rix,
|
||||
/{,usr/}bin/tr rix,
|
||||
/{,usr/}bin/uname rix,
|
||||
/{,usr/}bin/which rix,
|
||||
|
||||
@{HOME}/.java/fonts/** r,
|
||||
owner @{HOME}/.i2p/ rw,
|
||||
owner @{HOME}/.i2p/** rwk,
|
||||
|
||||
# Prevent spamming the logs
|
||||
deny owner @{HOME}/.java/ wk,
|
||||
deny @{HOME}/.fontconfig/ wk,
|
||||
deny @{HOME}/.java/fonts/** w,
|
||||
deny /dev/tty rw,
|
||||
deny /dev/pts/[0-9]* rw,
|
||||
deny @{PROC}/[0-9]*/fd/ r,
|
||||
deny /usr/local/share/fonts/ r,
|
||||
deny /var/cache/fontconfig/ wk,
|
||||
# Used by some versions of the Tanuki wrapper but never used by I2P
|
||||
deny /usr/share/java/hamcrest*.jar r,
|
||||
deny /usr/share/java/junit*.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
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# <b790979@klzlk.com>, 2011.
|
||||
# Translators:
|
||||
# PolishAnon <b790979@klzlk.com>, 2011
|
||||
# polacco <polacco@i2pmail.org>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2011-05-25 18:36+0000\n"
|
||||
"Last-Translator: PolishAnon <b790979@klzlk.com>\n"
|
||||
"Language-Team: Polish (http://www.transifex.net/projects/p/I2P/team/pl/)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2015-02-17 20:54+0000\n"
|
||||
"Last-Translator: polacco <polacco@i2pmail.org>\n"
|
||||
"Language-Team: Polish (http://www.transifex.com/projects/p/I2P/language/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pl\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@@ -32,7 +34,7 @@ msgstr "Uruchamianie"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Uruchom Przeglądarke I2P"
|
||||
msgstr "Uruchom przeglądarkę I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
@@ -46,12 +48,10 @@ msgstr "Zrestartuj I2P"
|
||||
msgid "Stop I2P"
|
||||
msgstr "Zatrzymaj I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Konfiguracja ikony zasobnika"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Czy ikona zasobnika powinna być aktywna?"
|
||||
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
# Translators:
|
||||
# Denis Blank <gribua@gmail.com>, 2011
|
||||
# LinuxChata, 2014
|
||||
# madjong <madjong@i2pmail.org>, 2014
|
||||
msgid ""
|
||||
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-06-22 10:20+0000\n"
|
||||
"Last-Translator: LinuxChata\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"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -50,7 +51,7 @@ msgstr "Зупинити I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Настройка трей-іконки"
|
||||
msgstr "Налаштування трей-іконки"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
|
||||
@@ -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,16 @@ public class BitField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -74,7 +74,6 @@ public class I2PSnarkUtil {
|
||||
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
|
||||
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
|
||||
public static final int MAX_CONNECTIONS = 16; // per torrent
|
||||
public static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
|
||||
public static final boolean DEFAULT_USE_DHT = true;
|
||||
@@ -97,7 +96,7 @@ public class I2PSnarkUtil {
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_banlist = new ConcurrentHashSet<Hash>();
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
_maxUpBW = SnarkManager.DEFAULT_MAX_UP_BW;
|
||||
_maxConnections = MAX_CONNECTIONS;
|
||||
_startupDelay = DEFAULT_STARTUP_DELAY;
|
||||
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
|
||||
@@ -106,8 +105,8 @@ public class I2PSnarkUtil {
|
||||
// This is used for both announce replies and .torrent file downloads,
|
||||
// so it must be available even if not connected to I2CP.
|
||||
// so much for multiple instances
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName);
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName + '-' + ctx.random().nextInt());
|
||||
//FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
|
||||
@@ -329,7 +328,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;
|
||||
@@ -457,7 +456,7 @@ public class I2PSnarkUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
public String getOurIPString() {
|
||||
Destination dest = getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
|
||||
@@ -29,6 +29,9 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
private int _consec;
|
||||
private int _consecNotRunning;
|
||||
private boolean _isIdle;
|
||||
private String _lastIn = "3";
|
||||
private String _lastOut = "3";
|
||||
private final Object _lock = new Object();
|
||||
|
||||
private static final long CHECK_TIME = 63*1000;
|
||||
private static final int MAX_CONSEC_IDLE = 4;
|
||||
@@ -46,16 +49,19 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
synchronized (_lock) {
|
||||
locked_timeReached();
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_timeReached() {
|
||||
if (_util.connected()) {
|
||||
boolean torrentRunning = false;
|
||||
boolean hasPeers = false;
|
||||
int peerCount = 0;
|
||||
for (PeerCoordinator pc : _pcs) {
|
||||
if (!pc.halted()) {
|
||||
torrentRunning = true;
|
||||
if (pc.getPeers() > 0) {
|
||||
hasPeers = true;
|
||||
break;
|
||||
}
|
||||
peerCount += pc.getPeers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,19 +79,22 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPeers) {
|
||||
if (_isIdle)
|
||||
restoreTunnels();
|
||||
if (peerCount > 0) {
|
||||
restoreTunnels(peerCount);
|
||||
} else {
|
||||
if (!_isIdle) {
|
||||
if (_consec++ >= MAX_CONSEC_IDLE)
|
||||
reduceTunnels();
|
||||
else
|
||||
restoreTunnels(1); // pretend we have one peer for now
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_isIdle = false;
|
||||
_consec = 0;
|
||||
_consecNotRunning = 0;
|
||||
_lastIn = "3";
|
||||
_lastOut = "3";
|
||||
}
|
||||
schedule(CHECK_TIME);
|
||||
}
|
||||
@@ -101,12 +110,13 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore tunnel count
|
||||
* Restore or adjust tunnel count based on current peer count
|
||||
* @param peerCount greater than zero
|
||||
*/
|
||||
private void restoreTunnels() {
|
||||
_isIdle = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
private void restoreTunnels(int peerCount) {
|
||||
if (_isIdle && _log.shouldLog(Log.INFO))
|
||||
_log.info("Restoring tunnels on activity");
|
||||
_isIdle = false;
|
||||
Map<String, String> opts = _util.getI2CPOptions();
|
||||
String i = opts.get("inbound.quantity");
|
||||
if (i == null)
|
||||
@@ -120,7 +130,30 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
String ob= opts.get("outbound.backupQuantity");
|
||||
if (ob == null)
|
||||
ob = "0";
|
||||
setTunnels(i, o, ib, ob);
|
||||
// we don't need more tunnels than we have peers, reduce if so
|
||||
// reduce to max(peerCount / 2, 2)
|
||||
int in, out;
|
||||
try {
|
||||
in = Integer.parseInt(i);
|
||||
} catch (NumberFormatException nfe) {
|
||||
in = 3;
|
||||
}
|
||||
try {
|
||||
out = Integer.parseInt(o);
|
||||
} catch (NumberFormatException nfe) {
|
||||
out = 3;
|
||||
}
|
||||
int target = Math.max(peerCount / 2, 2);
|
||||
if (target < in && in > 2) {
|
||||
in = target;
|
||||
i = Integer.toString(in);
|
||||
}
|
||||
if (target < out && out > 2) {
|
||||
out = target;
|
||||
o = Integer.toString(out);
|
||||
}
|
||||
if (!(_lastIn.equals(i) && _lastOut.equals(o)))
|
||||
setTunnels(i, o, ib, ob);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,12 +165,16 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
if (mgr != null) {
|
||||
I2PSession sess = mgr.getSession();
|
||||
if (sess != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New tunnel settings " + i + " / " + o + " / " + ib + " / " + ob);
|
||||
Properties newProps = new Properties();
|
||||
newProps.setProperty("inbound.quantity", i);
|
||||
newProps.setProperty("outbound.quantity", o);
|
||||
newProps.setProperty("inbound.backupQuantity", ib);
|
||||
newProps.setProperty("outbound.backupQuantity", ob);
|
||||
sess.updateOptions(newProps);
|
||||
_lastIn = i;
|
||||
_lastOut = o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class MetaInfo
|
||||
this.announce_list = announce_list;
|
||||
this.comment = null;
|
||||
this.created_by = null;
|
||||
this.creation_date = 0;
|
||||
this.creation_date = I2PAppContext.getGlobalContext().clock().now();
|
||||
|
||||
// TODO if we add a parameter for other keys
|
||||
//if (other != null) {
|
||||
@@ -444,7 +444,7 @@ public class MetaInfo
|
||||
|
||||
/**
|
||||
* The creation date (ms) or zero.
|
||||
* Not available for locally-created torrents.
|
||||
* As of 0.9.19, available for locally-created torrents.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public long getCreationDate() {
|
||||
@@ -595,6 +595,14 @@ public class MetaInfo
|
||||
m.put("announce", announce);
|
||||
if (announce_list != null)
|
||||
m.put("announce-list", announce_list);
|
||||
// misc. optional top-level stuff
|
||||
if (comment != null)
|
||||
m.put("comment", comment);
|
||||
if (created_by != null)
|
||||
m.put("created by", created_by);
|
||||
if (creation_date != 0)
|
||||
m.put("creation date", creation_date / 1000);
|
||||
|
||||
Map<String, BEValue> info = createInfoMap();
|
||||
m.put("info", info);
|
||||
// don't save this locally, we should only do this once
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -75,6 +75,8 @@ class PeerCheckerTask implements Runnable
|
||||
List<Peer> removed = new ArrayList<Peer>();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
boolean overBWLimit = coordinator.overUpBWLimit();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("peers: " + peerList.size() + " limit: " + uploadLimit + " overBW? " + overBWLimit);
|
||||
DHT dht = _util.getDHT();
|
||||
for (Peer peer : peerList) {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -234,7 +244,7 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -278,11 +288,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 +318,7 @@ class PeerConnectionOut implements Runnable
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -350,12 +371,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 +508,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 +538,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 +551,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 +592,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,15 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
@@ -52,7 +53,7 @@ import org.klomp.snark.dht.DHT;
|
||||
*/
|
||||
class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
|
||||
private final Log _log;
|
||||
|
||||
/**
|
||||
* External use by PeerMonitorTask only.
|
||||
@@ -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};
|
||||
@@ -98,7 +99,7 @@ class PeerCoordinator implements PeerListener
|
||||
* This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking.
|
||||
* External use by PeerMonitorTask only.
|
||||
*/
|
||||
final Queue<Peer> peers;
|
||||
final Deque<Peer> peers;
|
||||
|
||||
/**
|
||||
* Peers we heard about via PEX
|
||||
@@ -144,6 +145,7 @@ class PeerCoordinator implements PeerListener
|
||||
{
|
||||
_util = util;
|
||||
_random = util.getContext().random();
|
||||
_log = util.getContext().logManager().getLog(PeerCoordinator.class);
|
||||
this.id = id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
@@ -154,7 +156,7 @@ class PeerCoordinator implements PeerListener
|
||||
wantedPieces = new ArrayList<Piece>();
|
||||
setWantedPieces();
|
||||
partialPieces = new ArrayList<PartialPiece>(getMaxConnections() + 1);
|
||||
peers = new LinkedBlockingQueue<Peer>();
|
||||
peers = new LinkedBlockingDeque<Peer>();
|
||||
magnetState = new MagnetState(infohash, metainfo);
|
||||
pexPeers = new ConcurrentHashSet<PeerID>();
|
||||
|
||||
@@ -278,7 +280,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
return uploaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +288,7 @@ class PeerCoordinator implements PeerListener
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public void setUploaded(long up) {
|
||||
uploaded = up;
|
||||
uploaded.set(up);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +296,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
return downloaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,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)
|
||||
@@ -522,7 +530,10 @@ class PeerCoordinator implements PeerListener
|
||||
// Can't add to beginning since we converted from a List to a Queue
|
||||
// We can do this in Java 6 with a Deque
|
||||
//peers.add(0, peer);
|
||||
peers.add(peer);
|
||||
if (_util.getContext().random().nextInt(4) == 0)
|
||||
peers.push(peer);
|
||||
else
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
unchokePeer();
|
||||
|
||||
@@ -940,7 +951,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void uploaded(Peer peer, int size)
|
||||
{
|
||||
uploaded += size;
|
||||
uploaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@@ -951,7 +962,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void downloaded(Peer peer, int size)
|
||||
{
|
||||
downloaded += size;
|
||||
downloaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@@ -1000,7 +1011,7 @@ class PeerCoordinator implements PeerListener
|
||||
else
|
||||
{
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
@@ -1461,8 +1472,8 @@ class PeerCoordinator implements PeerListener
|
||||
public int allowedUploaders()
|
||||
{
|
||||
if (listener != null && listener.overUploadLimit(uploaders)) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Over limit, uploaders was: " + uploaders);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Over limit, uploaders was: " + uploaders);
|
||||
return uploaders - 1;
|
||||
} else if (uploaders < MAX_UPLOADERS)
|
||||
return uploaders + 1;
|
||||
|
||||
@@ -155,12 +155,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 +185,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,12 +225,17 @@ 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
|
||||
if (piece < 0
|
||||
@@ -227,8 +259,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;
|
||||
}
|
||||
|
||||
@@ -536,6 +574,58 @@ 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
|
||||
* @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) {
|
||||
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
|
||||
Request req = iter.next();
|
||||
if (req.getPiece() == piece && req.off == begin && req.len == length)
|
||||
iter.remove();
|
||||
}
|
||||
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 +633,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.
|
||||
|
||||
@@ -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)
|
||||
@@ -1288,7 +1303,8 @@ public class Snark
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
int limit = _util.getMaxUploaders();
|
||||
// debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Total uploaders: " + totalUploaders + " Limit: " + limit);
|
||||
return totalUploaders > limit;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -118,8 +120,8 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final String PROP_PRIVATETRACKERS = "i2psnark.privatetrackers";
|
||||
private static final String PROP_USE_DHT = "i2psnark.enableDHT";
|
||||
|
||||
public static final int MIN_UP_BW = 2;
|
||||
public static final int DEFAULT_MAX_UP_BW = 10;
|
||||
public static final int MIN_UP_BW = 10;
|
||||
public static final int DEFAULT_MAX_UP_BW = 25;
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final int DEFAULT_REFRESH_DELAY_SECS = 60;
|
||||
private static final int DEFAULT_PAGE_SIZE = 50;
|
||||
@@ -164,7 +166,7 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final Set<String> DEFAULT_TRACKER_ANNOUNCES;
|
||||
|
||||
/** host names for config form */
|
||||
public static final Set<String> KNOWN_OPENTRACKERS = new HashSet(Arrays.asList(new String[] {
|
||||
public static final Set<String> KNOWN_OPENTRACKERS = new HashSet<String>(Arrays.asList(new String[] {
|
||||
"tracker.welterde.i2p", "cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p",
|
||||
"opentracker.dg2.i2p", "w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p",
|
||||
"tracker.thebland.i2p", "s5ikrdyjwbcgxmqetxb3nyheizftms7euacuub2hic7defkh3xhq.b32.i2p",
|
||||
@@ -175,7 +177,7 @@ public class SnarkManager implements CompleteListener {
|
||||
}));
|
||||
|
||||
static {
|
||||
Set<String> ann = new HashSet();
|
||||
Set<String> ann = new HashSet<String>(8);
|
||||
for (int i = 1; i < DEFAULT_TRACKERS.length; i += 2) {
|
||||
if (DEFAULT_TRACKERS[i-1].equals("TheBland") && !SigType.ECDSA_SHA256_P256.isAvailable())
|
||||
continue;
|
||||
@@ -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) {
|
||||
@@ -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) {}
|
||||
|
||||
@@ -36,7 +36,9 @@ import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConvertToHash;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@@ -88,6 +90,8 @@ public class TrackerClient implements Runnable {
|
||||
private static final int DHT_ANNOUNCE_PEERS = 4;
|
||||
public static final int PORT = 6881;
|
||||
private static final int MAX_TRACKERS = 12;
|
||||
// tracker.welterde.i2p
|
||||
private static final Hash DSA_ONLY_TRACKER = ConvertToHash.getHash("cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p");
|
||||
|
||||
private final I2PSnarkUtil _util;
|
||||
private final MetaInfo meta;
|
||||
@@ -156,6 +160,7 @@ public class TrackerClient implements Runnable {
|
||||
consecutiveFails = 0;
|
||||
runStarted = false;
|
||||
_fastUnannounce = false;
|
||||
snark.setTrackerProblems(null);
|
||||
_thread = new I2PAppThread(this, _threadName + " #" + (++_runCount), true);
|
||||
_thread.start();
|
||||
started = true;
|
||||
@@ -362,12 +367,21 @@ public class TrackerClient implements Runnable {
|
||||
if (h == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad announce URL: [" + ann + ']');
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
// comment this out if tracker.welterde.i2p upgrades
|
||||
if (h.equals(DSA_ONLY_TRACKER)) {
|
||||
Destination dest = _util.getMyDestination();
|
||||
if (dest != null && dest.getSigType() != SigType.DSA_SHA1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Skipping incompatible tracker: " + ann);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (existing.size() >= MAX_TRACKERS) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Not using announce URL, we have enough: [" + ann + ']');
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
boolean rv = existing.add(h);
|
||||
if (!rv) {
|
||||
@@ -528,9 +542,9 @@ public class TrackerClient implements Runnable {
|
||||
!snark.isChecking() &&
|
||||
info.getSeedCount() > 100 &&
|
||||
coordinator.getPeerCount() <= 0 &&
|
||||
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
|
||||
_util.getContext().clock().now() > _startedOn + 30*60*1000 &&
|
||||
snark.getTotalLength() > 0 &&
|
||||
uploaded >= snark.getTotalLength() * 5 / 4) {
|
||||
uploaded >= snark.getTotalLength() / 2) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Auto stopping " + snark.getBaseName());
|
||||
snark.setAutoStoppable(false);
|
||||
@@ -898,7 +912,7 @@ public class TrackerClient implements Runnable {
|
||||
if (path == null || path.length() < 517 ||
|
||||
!path.startsWith("/"))
|
||||
return null;
|
||||
String[] parts = path.substring(1).split("/?&;", 2);
|
||||
String[] parts = path.substring(1).split("[/\\?&;]", 2);
|
||||
return ConvertToHash.getHash(parts[0]);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -842,7 +842,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending error " + msg + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap<String, Object>(4);
|
||||
List<Object> error = new ArrayList(2);
|
||||
List<Object> error = new ArrayList<Object>(2);
|
||||
error.add(Integer.valueOf(err));
|
||||
error.add(msg);
|
||||
map.put("e", error);
|
||||
@@ -1294,7 +1294,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
} else {
|
||||
List<byte[]> hashes;
|
||||
if (peers.isEmpty()) {
|
||||
hashes = Collections.EMPTY_LIST;
|
||||
hashes = Collections.emptyList();
|
||||
} else {
|
||||
hashes = new ArrayList<byte[]>(peers.size());
|
||||
for (Hash peer : peers) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
8
apps/i2psnark/java/src/org/klomp/snark/package.html
Normal file
8
apps/i2psnark/java/src/org/klomp/snark/package.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
I2P version of the snark bittorrent client, imported in 2005 and heavily enhanced
|
||||
to add a web UI, DHT support, and other features.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -358,6 +358,7 @@ class BasicServlet extends HttpServlet
|
||||
writeHeaders(response, content, content_length);
|
||||
response.setStatus(416);
|
||||
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
in.close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
@@ -1095,7 +1108,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
}
|
||||
} else if ("Save".equals(action)) {
|
||||
String dataDir = req.getParameter("dataDir");
|
||||
String dataDir = req.getParameter("nofilter_dataDir");
|
||||
boolean filesPublic = req.getParameter("filesPublic") != null;
|
||||
boolean autoStart = req.getParameter("autoStart") != null;
|
||||
String seedPct = req.getParameter("seedPct");
|
||||
@@ -2138,7 +2151,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
"<table border=\"0\"><tr><td>");
|
||||
|
||||
out.write(_("Data directory"));
|
||||
out.write(": <td><input name=\"dataDir\" size=\"80\" value=\"" + dataDir + "\" spellcheck=\"false\"></td>\n" +
|
||||
out.write(": <td><input name=\"nofilter_dataDir\" size=\"80\" value=\"" +
|
||||
DataHelper.escapeHTML(dataDir) + "\" spellcheck=\"false\"></td>\n" +
|
||||
|
||||
"<tr><td>");
|
||||
out.write(_("Files readable by all"));
|
||||
@@ -2268,13 +2282,13 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Inbound Settings"));
|
||||
out.write(":<td>");
|
||||
out.write(renderOptions(1, 6, 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, 6, 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));
|
||||
|
||||
@@ -2693,7 +2707,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append(" <b>")
|
||||
.append(_("Info hash"))
|
||||
.append(":</b> ")
|
||||
.append(hex)
|
||||
.append(hex.toUpperCase(Locale.US))
|
||||
.append("</td></tr>\n");
|
||||
|
||||
String announce = null;
|
||||
|
||||
7
apps/i2psnark/java/src/org/klomp/snark/web/package.html
Normal file
7
apps/i2psnark/java/src/org/klomp/snark/web/package.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The i2psnark user interface, implemented as a webapp in i2psnark.war.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
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
@@ -9,6 +9,7 @@ flac = audio/flac
|
||||
flv = video/x-flv
|
||||
iso = application/x-iso9660-image
|
||||
m4a = audio/mp4a-latm
|
||||
m4b = audio/mp4a-latm
|
||||
m4v = video/x-m4v
|
||||
mkv = video/x-matroska
|
||||
mobi = application/x-mobipocket-ebook
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<target name="jar" depends="builddep, compile, bundle-proxy, jarUpToDate, listChangedFiles" unless="jar.uptodate" >
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes.j.tr" value="" />
|
||||
<jar destfile="./build/i2ptunnel.jar" basedir="./build/obj" includes="**/*.class" excludes="**/EditBean.class **/IndexBean.class" >
|
||||
<jar destfile="./build/i2ptunnel.jar" basedir="./build/obj" includes="**/*.class" excludes="**/ui/*.class **/EditBean.class **/IndexBean.class" >
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.i2ptunnel.I2PTunnel" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
|
||||
@@ -73,7 +73,7 @@
|
||||
<attribute name="Workspace-Changes" value="${workspace.changes.j.tr}" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<jar destfile="./build/temp-beans.jar" basedir="./build/obj" includes="**/EditBean.class **/IndexBean.class" />
|
||||
<jar destfile="./build/temp-beans.jar" basedir="./build/obj" includes="**/ui/*.class **/EditBean.class **/IndexBean.class" />
|
||||
</target>
|
||||
|
||||
<target name="jarUpToDate">
|
||||
@@ -90,6 +90,36 @@
|
||||
</condition>
|
||||
</target>
|
||||
|
||||
<!-- Separate jar for general UI classes -->
|
||||
<target name="uiJar" depends="jar, uiJarUpToDate, listChangedFiles" unless="uiJar.uptodate" >
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes.j.tr" value="" />
|
||||
<jar destfile="./build/i2ptunnel-ui.jar" basedir="./build/obj" includes="**/ui/*.class" >
|
||||
<manifest>
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar i2ptunnel.jar" />
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
<attribute name="Built-By" value="${build.built-by}" />
|
||||
<attribute name="Build-Date" value="${build.timestamp}" />
|
||||
<attribute name="Base-Revision" value="${workspace.version}" />
|
||||
<attribute name="Workspace-Changes" value="${workspace.changes.j.tr}" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="uiJarUpToDate">
|
||||
<uptodate property="uiJar.uptodate" targetfile="build/i2ptunnel-ui.jar" >
|
||||
<srcfiles dir= "build/obj" includes="**/ui/*.class" />
|
||||
</uptodate>
|
||||
<condition property="shouldListChanges" >
|
||||
<and>
|
||||
<not>
|
||||
<isset property="uiJar.uptodate" />
|
||||
</not>
|
||||
<isset property="mtn.available" />
|
||||
</and>
|
||||
</condition>
|
||||
</target>
|
||||
|
||||
<!-- servlet translations go in the war, not the jar -->
|
||||
<target name="bundle" depends="compile, precompilejsp" unless="no.bundle">
|
||||
<!-- Update the messages_*.po files.
|
||||
@@ -185,6 +215,9 @@
|
||||
<target name="war" depends="precompilejsp, bundle, warUpToDate, listChangedFiles2" unless="war.uptodate" >
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes.w.tr" value="" />
|
||||
<copy todir="../jsp/WEB-INF/classes/net/i2p/i2ptunnel/ui">
|
||||
<fileset dir="build/obj/net/i2p/i2ptunnel/ui" />
|
||||
</copy>
|
||||
<copy file="build/obj/net/i2p/i2ptunnel/web/EditBean.class" todir="../jsp/WEB-INF/classes/net/i2p/i2ptunnel/web" />
|
||||
<copy file="build/obj/net/i2p/i2ptunnel/web/IndexBean.class" todir="../jsp/WEB-INF/classes/net/i2p/i2ptunnel/web" />
|
||||
<war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml"
|
||||
|
||||
@@ -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,19 +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.Log;
|
||||
import net.i2p.util.ReusableGZIPInputStream;
|
||||
|
||||
/**
|
||||
* This does the transparent gzip decompression on the client side.
|
||||
@@ -151,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++) {
|
||||
@@ -162,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 + "]");
|
||||
@@ -196,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 ");
|
||||
@@ -244,83 +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);
|
||||
// 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 {
|
||||
I2PTunnelClientBase.getClientExecutor().execute(new Pusher(pi, out));
|
||||
} catch (RejectedExecutionException ree) {
|
||||
// shouldn't happen
|
||||
throw ree;
|
||||
}
|
||||
out = po;
|
||||
}
|
||||
|
||||
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), 0);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, 0);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, 0);
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -83,7 +82,7 @@ import net.i2p.util.OrderedProperties;
|
||||
* An I2PTunnel tracks one or more I2PTunnelTasks and one or more I2PSessions.
|
||||
* Usually one of each.
|
||||
*
|
||||
* Todo: Most events are not listened to elsewhere, so error propagation is poor
|
||||
* TODO: Most events are not listened to elsewhere, so error propagation is poor
|
||||
*/
|
||||
public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
private final Log _log;
|
||||
@@ -540,6 +539,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
* This DOES update a running TunnelTask, but NOT the session.
|
||||
* A more efficient runClientOptions().
|
||||
*
|
||||
* Defaults in opts properties are not recommended, they may or may not be honored.
|
||||
*
|
||||
* @param opts non-null
|
||||
* @since 0.9.1
|
||||
*/
|
||||
@@ -885,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) {
|
||||
@@ -964,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) {
|
||||
@@ -1033,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 +
|
||||
@@ -1095,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) {
|
||||
@@ -1158,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) {
|
||||
@@ -1205,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) {
|
||||
@@ -1324,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");
|
||||
}
|
||||
}
|
||||
@@ -1508,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");
|
||||
}
|
||||
|
||||
@@ -1596,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);
|
||||
@@ -1662,7 +1670,14 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
private void runPing(String allargs, Logging l) {
|
||||
if (allargs.length() != 0) {
|
||||
_clientOptions.setProperty(I2Ping.PROP_COMMAND, allargs);
|
||||
I2PTunnelTask task = new I2Ping(l, ownDest, this, this);
|
||||
if (ownDest) {
|
||||
if (!_clientOptions.containsKey("inbound.nickname"))
|
||||
_clientOptions.setProperty("inbound.nickname", "I2Ping");
|
||||
if (!_clientOptions.containsKey("outbound.nickname"))
|
||||
_clientOptions.setProperty("outbound.nickname", "I2Ping");
|
||||
}
|
||||
I2PTunnelClientBase task = new I2Ping(l, ownDest, this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("pingTaskId", Integer.valueOf(task.getId()));
|
||||
} else {
|
||||
@@ -1739,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1758,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);
|
||||
@@ -1782,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) {
|
||||
@@ -1797,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");
|
||||
}
|
||||
|
||||
@@ -122,9 +117,11 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
int port = addr.getPort();
|
||||
i2ps = createI2PSocket(clientDest, port);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, null, null, mySockets,
|
||||
I2PTunnelRunner t = new I2PTunnelRunner(s, i2ps, sockLock, null, null, mySockets,
|
||||
(I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error connecting", ex);
|
||||
|
||||
@@ -16,12 +16,8 @@ import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
@@ -29,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;
|
||||
@@ -56,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.
|
||||
@@ -77,24 +77,26 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
// true if we are chained from a server.
|
||||
private boolean chained;
|
||||
|
||||
/** how long to wait before dropping an idle thread */
|
||||
private static final long HANDLER_KEEPALIVE_MS = 2*60*1000;
|
||||
private volatile ThreadPoolExecutor _executor;
|
||||
|
||||
/** this is ONLY for shared clients */
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
/**
|
||||
* We keep a static pool of socket handlers for all clients,
|
||||
* as there is no need for isolation on the client side.
|
||||
* Extending classes may use it for other purposes.
|
||||
* Not for use by servers, as there is no limit on threads.
|
||||
* Only destroy and replace a static shared client socket manager if it's been connected before
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private static volatile ThreadPoolExecutor _executor;
|
||||
private static int _executorThreadCount;
|
||||
private static final Object _executorLock = new Object();
|
||||
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
|
||||
@@ -106,46 +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());
|
||||
|
||||
synchronized (_executorLock) {
|
||||
if (_executor == null)
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -159,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
|
||||
@@ -175,20 +161,12 @@ 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());
|
||||
|
||||
synchronized (_executorLock) {
|
||||
if (_executor == null)
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
}
|
||||
|
||||
// normalize path so we can find it
|
||||
if (pkf != null) {
|
||||
File keyFile = new File(pkf);
|
||||
@@ -205,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.
|
||||
*
|
||||
@@ -294,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
|
||||
@@ -314,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
|
||||
@@ -326,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
|
||||
@@ -337,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 + "]");
|
||||
@@ -357,12 +305,56 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This may take a LONG time.
|
||||
* 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
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
protected static synchronized void killSharedClient() {
|
||||
socketManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -371,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
|
||||
@@ -386,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
|
||||
@@ -398,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
|
||||
@@ -415,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))
|
||||
@@ -468,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;
|
||||
}
|
||||
@@ -484,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();
|
||||
@@ -597,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) {
|
||||
@@ -640,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,49 +738,36 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
if (tcg != null) {
|
||||
_executor = tcg.getClientExecutor();
|
||||
} 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.
|
||||
// Never shut down.
|
||||
_executor = new TunnelControllerGroup.CustomThreadPoolExecutor();
|
||||
}
|
||||
while (open) {
|
||||
Socket s = ss.accept();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return may be null if no class has been instantiated
|
||||
* @since 0.8.8
|
||||
*/
|
||||
static ThreadPoolExecutor getClientExecutor() {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.8
|
||||
*/
|
||||
static void killClientExecutor() {
|
||||
synchronized (_executorLock) {
|
||||
if (_executor != null) {
|
||||
_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
|
||||
_executor.shutdownNow();
|
||||
_executor = null;
|
||||
}
|
||||
// kill the shared client, so that on restart in android
|
||||
// we won't latch onto the old one
|
||||
socketManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the connection just opened on the specified socket
|
||||
*
|
||||
@@ -721,37 +793,36 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not really needed for now but in case we want to add some hooks like afterExecute().
|
||||
*/
|
||||
private static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
|
||||
public CustomThreadPoolExecutor() {
|
||||
super(0, Integer.MAX_VALUE, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS,
|
||||
new SynchronousQueue<Runnable>(), new CustomThreadFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/** just to set the name and set Daemon */
|
||||
private static class CustomThreadFactory implements ThreadFactory {
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread rv = Executors.defaultThreadFactory().newThread(r);
|
||||
rv.setName("I2PTunnel Client Runner " + (++_executorThreadCount));
|
||||
rv.setDaemon(true);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -787,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.");
|
||||
@@ -822,7 +893,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
/**
|
||||
* Manage a connection in a separate thread. This only works if
|
||||
* you do not override manageConnection()
|
||||
* you do not override manageConnection().
|
||||
*
|
||||
* This is run in a thread from an unlimited-size thread pool,
|
||||
* so it may block or run indefinitely.
|
||||
*/
|
||||
protected abstract void clientConnectionRun(Socket s);
|
||||
}
|
||||
|
||||
@@ -59,26 +59,31 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
|
||||
public static final String AUTH_REALM = "I2P SSL Proxy";
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 405 Bad Method\r\n"+
|
||||
private final static String ERR_BAD_PROTOCOL =
|
||||
"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. "+
|
||||
"The Connect Proxy supports CONNECT requests ONLY. Other methods such as GET are not allowed - Maybe you wanted the HTTP Proxy?.<BR>")
|
||||
.getBytes();
|
||||
"The Connect Proxy supports CONNECT requests ONLY. Other methods such as GET are not allowed - Maybe you wanted the HTTP Proxy?.<BR>";
|
||||
|
||||
private final static byte[] ERR_LOCALHOST =
|
||||
("HTTP/1.1 403 Access Denied\r\n"+
|
||||
private final static String ERR_LOCALHOST =
|
||||
"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>")
|
||||
.getBytes();
|
||||
"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
|
||||
*/
|
||||
@@ -87,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())
|
||||
@@ -99,8 +99,6 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
}
|
||||
|
||||
setName("HTTPS Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,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
|
||||
@@ -273,7 +272,7 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
|
||||
Destination clientDest = _context.namingService().lookup(destination);
|
||||
if (clientDest == null) {
|
||||
byte[] header;
|
||||
String header;
|
||||
if (usingWWWProxy)
|
||||
header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN);
|
||||
else
|
||||
@@ -289,10 +288,12 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
if (usingWWWProxy)
|
||||
data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
else
|
||||
response = SUCCESS_RESPONSE;
|
||||
response = SUCCESS_RESPONSE.getBytes("UTF-8");
|
||||
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (IOException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
@@ -309,10 +310,10 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out) throws IOException {
|
||||
private static void writeErrorMessage(String errMessage, OutputStream out) throws IOException {
|
||||
if (out == null)
|
||||
return;
|
||||
out.write(errMessage);
|
||||
out.write(errMessage.getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
@@ -84,13 +87,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
/**
|
||||
* These are backups if the xxx.ht error page is missing.
|
||||
*/
|
||||
private final static byte[] ERR_REQUEST_DENIED =
|
||||
("HTTP/1.1 403 Access Denied\r\n" +
|
||||
private final static String ERR_REQUEST_DENIED =
|
||||
"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>").getBytes();
|
||||
"You attempted to connect to a non-I2P website or location.<BR>";
|
||||
|
||||
/*****
|
||||
private final static byte[] ERR_TIMEOUT =
|
||||
@@ -105,90 +110,109 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"the following Destination:<BR><BR>")
|
||||
.getBytes();
|
||||
*****/
|
||||
private final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n" +
|
||||
private final static String ERR_NO_OUTPROXY =
|
||||
"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 " +
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel").getBytes();
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel";
|
||||
|
||||
private final static byte[] ERR_AHELPER_CONFLICT =
|
||||
("HTTP/1.1 409 Conflict\r\n" +
|
||||
private final static String ERR_AHELPER_CONFLICT =
|
||||
"HTTP/1.1 409 Conflict\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"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 " +
|
||||
"than a host entry in your host database. " +
|
||||
"Someone could be trying to impersonate another eepsite, " +
|
||||
"or people have given two eepsites identical names.<p>" +
|
||||
"Someone could be trying to impersonate another website, " +
|
||||
"or people have given two websites identical names.<p>" +
|
||||
"You can resolve the conflict by considering which key you trust, " +
|
||||
"and either discarding the addresshelper link, " +
|
||||
"discarding the host entry from your host database, " +
|
||||
"or naming one of them differently.<p>").getBytes();
|
||||
"or naming one of them differently.<p>";
|
||||
|
||||
private final static byte[] ERR_AHELPER_NOTFOUND =
|
||||
("HTTP/1.1 404 Not Found\r\n" +
|
||||
private final static String ERR_AHELPER_NOTFOUND =
|
||||
"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. " +
|
||||
"It seems to be garbage data, or a mistyped b32. Check your URL " +
|
||||
"to try and fix the helper key to be either a b32 or a base64.").getBytes();
|
||||
"to try and fix the helper key to be either a b32 or a base64.";
|
||||
|
||||
private final static byte[] ERR_AHELPER_NEW =
|
||||
("HTTP/1.1 409 New Address\r\n" +
|
||||
private final static String ERR_AHELPER_NEW =
|
||||
"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. " +
|
||||
"You may either save the destination for this host name to your address book, or remember it only until your router restarts. " +
|
||||
"If you save it to your address book, you will not see this message again. " +
|
||||
"If you do not wish to visit this host, click the \"back\" button on your browser.").getBytes();
|
||||
"If you do not wish to visit this host, click the \"back\" button on your browser.";
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 403 Bad Protocol\r\n" +
|
||||
private final static String ERR_BAD_PROTOCOL =
|
||||
"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. " +
|
||||
"The I2P HTTP Proxy supports HTTP and HTTPS requests only. Other protocols such as FTP are not allowed.<BR>").getBytes();
|
||||
"The I2P HTTP Proxy supports HTTP and HTTPS requests only. Other protocols such as FTP are not allowed.<BR>";
|
||||
|
||||
private final static byte[] ERR_BAD_URI =
|
||||
("HTTP/1.1 403 Bad URI\r\n" +
|
||||
private final static String ERR_BAD_URI =
|
||||
"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. " +
|
||||
"If you clicked e.g. a forum link, check the end of the URI for any characters the browser has mistakenly added on.<BR>").getBytes();
|
||||
"If you clicked e.g. a forum link, check the end of the URI for any characters the browser has mistakenly added on.<BR>";
|
||||
|
||||
private final static byte[] ERR_LOCALHOST =
|
||||
("HTTP/1.1 403 Access Denied\r\n" +
|
||||
private final static String ERR_LOCALHOST =
|
||||
"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>").getBytes();
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>";
|
||||
|
||||
private final static byte[] ERR_INTERNAL_SSL =
|
||||
("HTTP/1.1 403 SSL Rejected\r\n" +
|
||||
private final static String ERR_INTERNAL_SSL =
|
||||
"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." +
|
||||
"You may change the configuration in I2PTunnel").getBytes();
|
||||
"You may change the configuration in I2PTunnel";
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -197,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
|
||||
*/
|
||||
@@ -213,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, ", ");
|
||||
@@ -226,9 +247,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
|
||||
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
@@ -293,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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,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();
|
||||
@@ -464,10 +491,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(_log.shouldLog(Log.WARN)) {
|
||||
_log.warn(getPrefix(requestId) + "Bad request [" + request + "]", use);
|
||||
}
|
||||
out.write(getErrorPage("baduri", ERR_BAD_URI));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
s.close();
|
||||
try {
|
||||
out.write(getErrorPage("baduri", ERR_BAD_URI).getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -596,12 +628,20 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(_log.shouldLog(Log.WARN)) {
|
||||
_log.warn(getPrefix(requestId) + "Could not find destination for " + ahelperKey);
|
||||
}
|
||||
byte[] header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
|
||||
out.write(header);
|
||||
out.write(("<p>" + _("This seems to be a bad destination:") + " " + ahelperKey + " " + _("i2paddresshelper cannot help you with a destination like that!") + "</p>").getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
// XXX: should closeSocket(s) be in a finally block?
|
||||
closeSocket(s);
|
||||
String header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
|
||||
try {
|
||||
out.write(header.getBytes("UTF-8"));
|
||||
out.write(("<p>" + _("This seems to be a bad destination:") + " " + ahelperKey + " " +
|
||||
_("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
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ahelperKey = _dest.toBase64();
|
||||
@@ -614,7 +654,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// Store in local HashMap unless there is conflict
|
||||
String old = addressHelpers.putIfAbsent(destination.toLowerCase(Locale.US), ahelperKey);
|
||||
ahelperNew = old == null;
|
||||
if((!ahelperNew) && !old.equals(ahelperKey)) {
|
||||
// inr address helper links without trailing '=', so omit from comparison
|
||||
if ((!ahelperNew) && !old.replace("=", "").equals(ahelperKey.replace("=", ""))) {
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if(_log.shouldLog(Log.WARN)) {
|
||||
@@ -644,11 +685,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
|
||||
// Did addresshelper key conflict?
|
||||
if(ahelperConflict) {
|
||||
try {
|
||||
// convert ahelperKey to b32
|
||||
String alias = getHostName(ahelperKey);
|
||||
if(alias.equals("i2p")) {
|
||||
// bad ahelperKey
|
||||
byte[] header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
|
||||
String header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
|
||||
writeErrorMessage(header, out, targetRequest, false, destination);
|
||||
} else {
|
||||
String trustedURL = requestURI.toASCIIString();
|
||||
@@ -662,14 +704,19 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
break;
|
||||
}
|
||||
String conflictURL = conflictURI.toASCIIString();
|
||||
byte[] header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
|
||||
out.write(header);
|
||||
out.write(_("To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>.", trustedURL, conflictURL).getBytes("UTF-8"));
|
||||
out.write(("</p></div>").getBytes());
|
||||
String header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
|
||||
out.write(header.getBytes("UTF-8"));
|
||||
out.write(_("To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>.",
|
||||
trustedURL, conflictURL).getBytes("UTF-8"));
|
||||
out.write("</p></div>".getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
}
|
||||
reader.drain();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
reader.drain();
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
} // end query processing
|
||||
@@ -699,10 +746,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
} else if(hostLowerCase.equals("localhost") || host.equals("127.0.0.1") ||
|
||||
host.startsWith("192.168.") || host.equals("[::1]")) {
|
||||
// if somebody is trying to get to 192.168.example.com, oh well
|
||||
out.write(getErrorPage("localhost", ERR_LOCALHOST));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
s.close();
|
||||
try {
|
||||
out.write(getErrorPage("localhost", ERR_LOCALHOST).getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
} else if(host.contains(".") || host.startsWith("[")) {
|
||||
if (Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USE_OUTPROXY_PLUGIN, "true"))) {
|
||||
@@ -747,10 +799,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
_log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!");
|
||||
}
|
||||
l.log("No outproxy found for the request.");
|
||||
out.write(getErrorPage("noproxy", ERR_NO_OUTPROXY));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
s.close();
|
||||
try {
|
||||
out.write(getErrorPage("noproxy", ERR_NO_OUTPROXY).getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
destination = currentProxy;
|
||||
@@ -768,10 +825,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("NODOTS, NOI2P: " + request);
|
||||
}
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
s.close();
|
||||
try {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED).getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
} // end host name processing
|
||||
|
||||
@@ -897,7 +959,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW);
|
||||
}
|
||||
if(user != null && pw != null) {
|
||||
newRequest.append("Proxy-Authorization: Basic ").append(Base64.encode((user + ':' + pw).getBytes(), true)) // true = use standard alphabet
|
||||
newRequest.append("Proxy-Authorization: Basic ")
|
||||
.append(Base64.encode((user + ':' + pw).getBytes("UTF-8"), true)) // true = use standard alphabet
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
@@ -914,13 +977,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
|
||||
if(method == null || (destination == null && !usingInternalOutproxy)) {
|
||||
//l.log("No HTTP method found in the request.");
|
||||
if (protocol != null && "http".equals(protocol.toLowerCase(Locale.US))) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
} else {
|
||||
out.write(getErrorPage("protocol", ERR_BAD_PROTOCOL));
|
||||
try {
|
||||
if (protocol != null && "http".equals(protocol.toLowerCase(Locale.US))) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED).getBytes("UTF-8"));
|
||||
} else {
|
||||
out.write(getErrorPage("protocol", ERR_BAD_PROTOCOL).getBytes("UTF-8"));
|
||||
}
|
||||
writeFooter(out);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
writeFooter(out);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -938,23 +1006,33 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
|
||||
}
|
||||
}
|
||||
out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes());
|
||||
writeFooter(out);
|
||||
s.close();
|
||||
try {
|
||||
out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve local proxy files (images, css linked from error pages)
|
||||
// Ignore all the headers
|
||||
if(usingInternalServer) {
|
||||
// disable the add form if address helper is disabled
|
||||
if(internalPath.equals("/add") &&
|
||||
Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
|
||||
out.write(ERR_HELPER_DISABLED);
|
||||
} else {
|
||||
LocalHTTPServer.serveLocalFile(out, method, internalPath, internalRawQuery, _proxyNonce);
|
||||
if (usingInternalServer) {
|
||||
try {
|
||||
// disable the add form if address helper is disabled
|
||||
if(internalPath.equals("/add") &&
|
||||
Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
|
||||
out.write(ERR_HELPER_DISABLED.getBytes("UTF-8"));
|
||||
} else {
|
||||
LocalHTTPServer.serveLocalFile(out, method, internalPath, internalRawQuery, _proxyNonce);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -966,13 +1044,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
byte[] response;
|
||||
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
|
||||
data = null;
|
||||
response = SUCCESS_RESPONSE;
|
||||
response = SUCCESS_RESPONSE.getBytes("UTF-8");
|
||||
} else {
|
||||
data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
response = null;
|
||||
}
|
||||
Thread t = new I2PTunnelOutproxyRunner(s, outSocket, sockLock, data, response, onTimeout);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -990,9 +1070,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(_log.shouldLog(Log.WARN)) {
|
||||
_log.warn(getPrefix(requestId) + "Could not find destination for " + addressHelper);
|
||||
}
|
||||
byte[] header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
|
||||
writeErrorMessage(header, out, targetRequest, false, destination);
|
||||
s.close();
|
||||
String header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
|
||||
try {
|
||||
writeErrorMessage(header, out, targetRequest, false, destination);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if("i2p".equals(host)) {
|
||||
@@ -1022,7 +1107,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
|
||||
}
|
||||
byte[] header;
|
||||
String header;
|
||||
String jumpServers = null;
|
||||
String extraMessage = null;
|
||||
if(usingWWWProxy) {
|
||||
@@ -1039,16 +1124,26 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
jumpServers = DEFAULT_JUMP_SERVERS;
|
||||
}
|
||||
}
|
||||
writeErrorMessage(header, extraMessage, out, targetRequest, usingWWWProxy, destination, jumpServers);
|
||||
s.close();
|
||||
try {
|
||||
writeErrorMessage(header, extraMessage, out, targetRequest, usingWWWProxy, destination, jumpServers);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (method.toUpperCase(Locale.US).equals("CONNECT") &&
|
||||
!usingWWWProxy &&
|
||||
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_INTERNAL_SSL))) {
|
||||
writeErrorMessage(ERR_INTERNAL_SSL, out, targetRequest, false, destination);
|
||||
s.close();
|
||||
try {
|
||||
writeErrorMessage(ERR_INTERNAL_SSL, out, targetRequest, false, destination);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("SSL to i2p destinations denied by configuration: " + targetRequest);
|
||||
return;
|
||||
@@ -1060,8 +1155,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(ahelperNew && "GET".equals(method) &&
|
||||
(userAgent == null || !userAgent.startsWith("Wget")) &&
|
||||
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
|
||||
writeHelperSaveForm(out, destination, ahelperKey, targetRequest, referer);
|
||||
s.close();
|
||||
try {
|
||||
writeHelperSaveForm(out, destination, ahelperKey, targetRequest, referer);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1074,10 +1174,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
if(_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Auto redirecting to " + uri);
|
||||
}
|
||||
out.write(("HTTP/1.1 301 Address Helper Accepted\r\n" +
|
||||
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"));
|
||||
s.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
closeSocket(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1091,6 +1198,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
sktOpts.setPort(remotePort);
|
||||
I2PSocket i2ps = createI2PSocket(clientDest, sktOpts);
|
||||
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
Thread t;
|
||||
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
|
||||
byte[] data;
|
||||
byte[] response;
|
||||
@@ -1099,15 +1207,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
response = null;
|
||||
} else {
|
||||
data = null;
|
||||
response = SUCCESS_RESPONSE;
|
||||
response = SUCCESS_RESPONSE.getBytes("UTF-8");
|
||||
}
|
||||
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
t.start();
|
||||
t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
} else {
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
Thread t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
t.start();
|
||||
t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
}
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch(IOException ex) {
|
||||
if(_log.shouldLog(Log.INFO)) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
@@ -1141,7 +1250,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
String s = getTunnel().getClientOptions().getProperty(PROP_SSL_OUTPROXIES);
|
||||
if (s == null)
|
||||
return null;
|
||||
String[] p = s.split(", ");
|
||||
String[] p = s.split("[,; \r\n\t]");
|
||||
if (p.length == 0)
|
||||
return null;
|
||||
// todo doesn't check for ""
|
||||
@@ -1152,22 +1261,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
|
||||
/** @since 0.8.7 */
|
||||
private void writeHelperSaveForm(OutputStream out, String destination, String ahelperKey,
|
||||
private void writeHelperSaveForm(OutputStream outs, String destination, String ahelperKey,
|
||||
String targetRequest, String referer) throws IOException {
|
||||
if(out == null) {
|
||||
if(outs == null)
|
||||
return;
|
||||
}
|
||||
byte[] header = getErrorPage("ahelper-new", ERR_AHELPER_NEW);
|
||||
Writer out = new BufferedWriter(new OutputStreamWriter(outs, "UTF-8"));
|
||||
String header = getErrorPage("ahelper-new", ERR_AHELPER_NEW);
|
||||
out.write(header);
|
||||
out.write(("<table><tr><td class=\"mediumtags\" align=\"right\">" + _("Host") +
|
||||
"</td><td class=\"mediumtags\">" + destination + "</td></tr>\n").getBytes());
|
||||
out.write("<table><tr><td class=\"mediumtags\" align=\"right\">" + _("Host") +
|
||||
"</td><td class=\"mediumtags\">" + destination + "</td></tr>\n");
|
||||
try {
|
||||
String b32 = Base32.encode(SHA256Generator.getInstance().calculateHash(Base64.decode(ahelperKey)).getData());
|
||||
out.write(("<tr><td class=\"mediumtags\" align=\"right\">" + _("Base 32") + "</td>" +
|
||||
"<td><a href=\"http://" + b32 + ".b32.i2p/\">" + b32 + ".b32.i2p</a></td></tr>").getBytes());
|
||||
out.write("<tr><td class=\"mediumtags\" align=\"right\">" + _("Base 32") + "</td>" +
|
||||
"<td><a href=\"http://" + b32 + ".b32.i2p/\">" + b32 + ".b32.i2p</a></td></tr>");
|
||||
} catch(Exception e) {
|
||||
}
|
||||
out.write(("<tr><td class=\"mediumtags\" align=\"right\">" + _("Destination") + "</td><td>" +
|
||||
out.write("<tr><td class=\"mediumtags\" align=\"right\">" + _("Destination") + "</td><td>" +
|
||||
"<textarea rows=\"1\" style=\"height: 4em; min-width: 0; min-height: 0;\" cols=\"70\" wrap=\"off\" readonly=\"readonly\" >" +
|
||||
ahelperKey + "</textarea></td></tr></table>\n" +
|
||||
"<hr><div class=\"formaction\">" +
|
||||
@@ -1178,18 +1287,19 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"<input type=\"hidden\" name=\"host\" value=\"" + destination + "\">\n" +
|
||||
"<input type=\"hidden\" name=\"dest\" value=\"" + ahelperKey + "\">\n" +
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"" + _proxyNonce + "\">\n" +
|
||||
"<button type=\"submit\" class=\"accept\" name=\"router\" value=\"router\">" + _("Save {0} to router address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8"));
|
||||
"<button type=\"submit\" class=\"accept\" name=\"router\" value=\"router\">" +
|
||||
_("Save {0} to router address book and continue to website", destination) + "</button><br>\n");
|
||||
if(_context.namingService().getName().equals("BlockfileNamingService")) {
|
||||
// only blockfile supports multiple books
|
||||
out.write(("<br><button type=\"submit\" name=\"master\" value=\"master\">" + _("Save {0} to master address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8"));
|
||||
out.write(("<button type=\"submit\" name=\"private\" value=\"private\">" + _("Save {0} to private address book and continue to eepsite", destination) + "</button>\n").getBytes("UTF-8"));
|
||||
out.write("<br><button type=\"submit\" name=\"master\" value=\"master\">" + _("Save {0} to master address book and continue to website", destination) + "</button><br>\n");
|
||||
out.write("<button type=\"submit\" name=\"private\" value=\"private\">" + _("Save {0} to private address book and continue to website", destination) + "</button>\n");
|
||||
}
|
||||
// Firefox (and others?) don't send referer to meta refresh target, which is
|
||||
// what the jump servers use, so this isn't that useful.
|
||||
if (referer != null)
|
||||
out.write(("<input type=\"hidden\" name=\"referer\" value=\"" + referer + "\">\n").getBytes("UTF-8"));
|
||||
out.write(("<input type=\"hidden\" name=\"url\" value=\"" + targetRequest + "\">\n" +
|
||||
"</form></div></div>").getBytes());
|
||||
out.write("<input type=\"hidden\" name=\"referer\" value=\"" + referer + "\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"url\" value=\"" + targetRequest + "\">\n" +
|
||||
"</form></div></div>");
|
||||
writeFooter(out);
|
||||
}
|
||||
|
||||
@@ -1291,11 +1401,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
return lc.equals("http") || lc.equals("https");
|
||||
}
|
||||
|
||||
private final static byte[] ERR_HELPER_DISABLED =
|
||||
("HTTP/1.1 403 Disabled\r\n" +
|
||||
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").getBytes();
|
||||
"Address helpers disabled";
|
||||
|
||||
/**
|
||||
* Change various parts of the URI.
|
||||
@@ -1384,7 +1496,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
keystart = i + 1;
|
||||
valstart = -1;
|
||||
} else if(c == '=') {
|
||||
} else if (c == '=' && valstart < 0) {
|
||||
// end of key
|
||||
key = query.substring(keystart, i);
|
||||
valstart = i + 1;
|
||||
@@ -1394,28 +1506,33 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
/****
|
||||
private static String[] tests = {
|
||||
"", "foo", "foo=bar", "&", "&=&", "===", "&&",
|
||||
"i2paddresshelper=foo",
|
||||
"i2paddresshelpe=foo",
|
||||
"2paddresshelper=foo",
|
||||
"i2paddresshelper=%66oo",
|
||||
"%692paddresshelper=foo",
|
||||
"i2paddresshelper=foo&a=b",
|
||||
"a=b&i2paddresshelper=foo",
|
||||
"a=b&i2paddresshelper&c=d",
|
||||
"a=b&i2paddresshelper=foo&c=d",
|
||||
"a=b;i2paddresshelper=foo;c=d",
|
||||
"a=b&i2paddresshelper=foo&c"
|
||||
"", "foo", "foo=bar", "&", "&=&", "===", "&&",
|
||||
"i2paddresshelper=foo",
|
||||
"i2paddresshelpe=foo",
|
||||
"2paddresshelper=foo",
|
||||
"i2paddresshelper=%66oo",
|
||||
"%692paddresshelper=foo",
|
||||
"i2paddresshelper=foo&a=b",
|
||||
"a=b&i2paddresshelper=foo",
|
||||
"a=b&i2paddresshelper&c=d",
|
||||
"a=b&i2paddresshelper=foo&c=d",
|
||||
"a=b;i2paddresshelper=foo;c=d",
|
||||
"a=b&i2paddresshelper=foo&c",
|
||||
"a=b&i2paddresshelper=foo==&c",
|
||||
"a=b&i2paddresshelper=foo%3d%3d&c",
|
||||
"a=b&i2paddresshelper=f%6f%6F==&c",
|
||||
"a=b&i2paddresshelper=foo&i2paddresshelper=bar&c",
|
||||
"a=b&i2paddresshelper=foo&c%3F%3f%26%3b%3B%3d%3Dc=x%3F%3f%26%3b%3B%3d%3Dx"
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < tests.length; i++) {
|
||||
String[] s = removeHelper(tests[i]);
|
||||
if (s != null)
|
||||
System.out.println("Test \"" + tests[i] + "\" q=\"" + s[0] + "\" h=\"" + s[1] + "\"");
|
||||
else
|
||||
System.out.println("Test \"" + tests[i] + "\" no match");
|
||||
}
|
||||
for (int i = 0; i < tests.length; i++) {
|
||||
String[] s = removeHelper(tests[i]);
|
||||
if (s != null)
|
||||
System.out.println("Test \"" + tests[i] + "\" q=\"" + s[0] + "\" h=\"" + s[1] + "\"");
|
||||
else
|
||||
System.out.println("Test \"" + tests[i] + "\" no match");
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -60,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
|
||||
@@ -71,33 +76,35 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
|
||||
protected final List<String> _proxyList;
|
||||
|
||||
protected final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
protected final static String ERR_NO_OUTPROXY =
|
||||
"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 "+
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel";
|
||||
|
||||
protected final static byte[] ERR_DESTINATION_UNKNOWN =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n" +
|
||||
protected final static String ERR_DESTINATION_UNKNOWN =
|
||||
"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 " +
|
||||
"wrong BASE64 I2P Destination or the link you are following is " +
|
||||
"bad. The host (or the WWW proxy, if you're using one) could also " +
|
||||
"be temporarily offline. You may want to <b>retry</b>. " +
|
||||
"Could not find the following Destination:<BR><BR><div>").getBytes();
|
||||
"Could not find the following Destination:<BR><BR><div>";
|
||||
|
||||
protected final static byte[] SUCCESS_RESPONSE =
|
||||
("HTTP/1.1 200 Connection Established\r\n"+
|
||||
protected final static String SUCCESS_RESPONSE =
|
||||
"HTTP/1.1 200 Connection Established\r\n"+
|
||||
"Proxy-agent: I2P\r\n"+
|
||||
"\r\n")
|
||||
.getBytes();
|
||||
"\r\n";
|
||||
|
||||
private final byte[] _proxyNonce;
|
||||
private final ConcurrentHashMap<String, NonceInfo> _nonces;
|
||||
@@ -214,11 +221,14 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
// see TunnelController.setSessionOptions()
|
||||
String proxies = props.getProperty("proxyList");
|
||||
if (proxies != null) {
|
||||
StringTokenizer tok = new StringTokenizer(proxies, ", ");
|
||||
StringTokenizer tok = new StringTokenizer(proxies, ",; \r\n\t");
|
||||
synchronized(_proxyList) {
|
||||
_proxyList.clear();
|
||||
while (tok.hasMoreTokens())
|
||||
_proxyList.add(tok.nextToken().trim());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String p = tok.nextToken().trim();
|
||||
if (p.length() > 0)
|
||||
_proxyList.add(p);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
synchronized(_proxyList) {
|
||||
@@ -484,7 +494,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
* @return non-null
|
||||
* @since 0.9.4 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
protected byte[] getErrorPage(String base, byte[] backup) {
|
||||
protected String getErrorPage(String base, String backup) {
|
||||
return getErrorPage(_context, base, backup);
|
||||
}
|
||||
|
||||
@@ -499,7 +509,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
* @return non-null
|
||||
* @since 0.9.4 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
protected static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {
|
||||
protected static String getErrorPage(I2PAppContext ctx, String base, String backup) {
|
||||
File errorDir = new File(ctx.getBaseDir(), "docs");
|
||||
File file = new File(errorDir, base + "-header.ht");
|
||||
try {
|
||||
@@ -515,7 +525,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
/**
|
||||
* @since 0.9.4 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
private static byte[] readFile(I2PAppContext ctx, File file) throws IOException {
|
||||
private static String readFile(I2PAppContext ctx, File file) throws IOException {
|
||||
Reader reader = null;
|
||||
char[] buf = new char[512];
|
||||
StringBuilder out = new StringBuilder(2048);
|
||||
@@ -525,7 +535,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
while((len = reader.read(buf)) > 0) {
|
||||
out.append(buf, 0, len);
|
||||
}
|
||||
return out.toString().getBytes("UTF-8");
|
||||
return out.toString();
|
||||
} finally {
|
||||
try {
|
||||
if(reader != null)
|
||||
@@ -578,7 +588,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
boolean usingWWWProxy, String wwwProxy, long requestId) {
|
||||
if (out == null)
|
||||
return;
|
||||
byte[] header;
|
||||
String header;
|
||||
if (usingWWWProxy)
|
||||
header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp", ERR_DESTINATION_UNKNOWN);
|
||||
else
|
||||
@@ -607,10 +617,12 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
error = usingWWWProxy ? "nolsp" : "nols";
|
||||
} else if (status == MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION) {
|
||||
error = usingWWWProxy ? "encp" : "enc";
|
||||
} else if (status == I2PSocketException.STATUS_CONNECTION_RESET) {
|
||||
error = usingWWWProxy ? "resetp" : "reset";
|
||||
} else {
|
||||
error = usingWWWProxy ? "dnfp" : "dnf";
|
||||
}
|
||||
byte[] header = getErrorPage(error, ERR_DESTINATION_UNKNOWN);
|
||||
String header = getErrorPage(error, ERR_DESTINATION_UNKNOWN);
|
||||
String message = ise != null ? ise.getLocalizedMessage() : "unknown error";
|
||||
try {
|
||||
writeErrorMessage(header, message, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
@@ -621,7 +633,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
* No jump servers or extra message
|
||||
* @since 0.9.14
|
||||
*/
|
||||
protected void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
protected void writeErrorMessage(String errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
writeErrorMessage(errMessage, null, out, targetRequest, usingWWWProxy, wwwProxy, null);
|
||||
}
|
||||
@@ -631,17 +643,17 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
* @param jumpServers comma- or space-separated list, or null
|
||||
* @since 0.9.14 moved from subclasses
|
||||
*/
|
||||
protected void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
protected void writeErrorMessage(String errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy, String jumpServers) throws IOException {
|
||||
writeErrorMessage(errMessage, null, out, targetRequest, usingWWWProxy, wwwProxy, jumpServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* No jump servers
|
||||
* @param extraMessage extra message
|
||||
* @param extraMessage extra message or null, will be HTML-escaped
|
||||
* @since 0.9.14
|
||||
*/
|
||||
protected void writeErrorMessage(byte[] errMessage, String extraMessage,
|
||||
protected void writeErrorMessage(String errMessage, String extraMessage,
|
||||
OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
writeErrorMessage(errMessage, extraMessage, out, targetRequest, usingWWWProxy, wwwProxy, null);
|
||||
@@ -649,30 +661,34 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
|
||||
/**
|
||||
* @param jumpServers comma- or space-separated list, or null
|
||||
* @param extraMessage extra message
|
||||
* @param extraMessage extra message or null, will be HTML-escaped
|
||||
* @since 0.9.14
|
||||
*/
|
||||
protected void writeErrorMessage(byte[] errMessage, String extraMessage,
|
||||
OutputStream out, String targetRequest,
|
||||
protected void writeErrorMessage(String errMessage, String extraMessage,
|
||||
OutputStream outs, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy,
|
||||
String jumpServers) throws IOException {
|
||||
if (out == null)
|
||||
if (outs == null)
|
||||
return;
|
||||
Writer out = new BufferedWriter(new OutputStreamWriter(outs, "UTF-8"));
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
String uri = targetRequest.replace("&", "&");
|
||||
out.write("<a href=\"".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
String uri = DataHelper.escapeHTML(targetRequest);
|
||||
out.write("<a href=\"");
|
||||
out.write(uri);
|
||||
out.write("\">");
|
||||
if (targetRequest.length() > 80)
|
||||
out.write(DataHelper.escapeHTML(targetRequest.substring(0, 75)) + "…");
|
||||
else
|
||||
out.write(uri);
|
||||
out.write("</a>");
|
||||
if (usingWWWProxy) {
|
||||
out.write(("<br><br><b>").getBytes());
|
||||
out.write(_("HTTP Outproxy").getBytes("UTF-8"));
|
||||
out.write((":</b> " + wwwProxy).getBytes());
|
||||
out.write("<br><br><b>");
|
||||
out.write(_("HTTP Outproxy"));
|
||||
out.write(":</b> " + wwwProxy);
|
||||
}
|
||||
if (extraMessage != null) {
|
||||
out.write(("<br><br><b>" + extraMessage + "</b>").getBytes());
|
||||
out.write("<br><br><b>" + DataHelper.escapeHTML(extraMessage) + "</b>");
|
||||
}
|
||||
if (jumpServers != null && jumpServers.length() > 0) {
|
||||
boolean first = true;
|
||||
@@ -706,21 +722,23 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
out.write("<br><br>".getBytes());
|
||||
out.write(_("Click a link below to look for an address helper by using a \"jump\" service:").getBytes("UTF-8"));
|
||||
out.write("<br>\n".getBytes());
|
||||
out.write("<br><br><h3>");
|
||||
out.write(_("Click a link below for an address helper from a jump service"));
|
||||
out.write("</h3>\n");
|
||||
} else {
|
||||
out.write("<br>");
|
||||
}
|
||||
out.write("<br><a href=\"".getBytes());
|
||||
out.write(jurl.getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">".getBytes());
|
||||
out.write("<a href=\"");
|
||||
out.write(jurl);
|
||||
out.write(uri);
|
||||
out.write("\">");
|
||||
// Translators: parameter is a host name
|
||||
out.write(_("{0} jump service", jumphost).getBytes());
|
||||
out.write("</a>\n".getBytes());
|
||||
out.write(_("{0} jump service", jumphost));
|
||||
out.write("</a>\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
out.write("</div>".getBytes());
|
||||
out.write("</div>");
|
||||
writeFooter(out);
|
||||
}
|
||||
|
||||
@@ -731,12 +749,29 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
* @since 0.9.14 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
public static void writeFooter(OutputStream out) throws IOException {
|
||||
out.write(getFooter().getBytes("UTF-8"));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes.
|
||||
*
|
||||
* Public only for LocalHTTPServer, not for general use
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public static void writeFooter(Writer out) throws IOException {
|
||||
out.write(getFooter());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private static String getFooter() {
|
||||
// The css is hiding this div for now, but we'll keep it here anyway
|
||||
// Tag the strings below for translation if we unhide it.
|
||||
out.write("<div class=\"proxyfooter\"><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></div></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
buf.append("<div class=\"proxyfooter\"><p><i>I2P HTTP Proxy Server<br>Generated on: ")
|
||||
.append(new Date().toString())
|
||||
.append("</i></div></body></html>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
@@ -86,7 +89,8 @@ public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
// ignore
|
||||
}
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// t2 = fromI2P now run inline
|
||||
//t2.join(30*1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -11,6 +12,7 @@ import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -63,14 +65,22 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private static final String SERVER_HEADER = "Server";
|
||||
private static final String X_POWERED_BY_HEADER = "X-Powered-By";
|
||||
private static final String[] SERVER_SKIPHEADERS = {SERVER_HEADER, X_POWERED_BY_HEADER};
|
||||
/** timeout for first request line */
|
||||
private static final long HEADER_TIMEOUT = 15*1000;
|
||||
/** total timeout for the request and all the headers */
|
||||
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"+
|
||||
@@ -78,12 +88,11 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
"\r\n"+
|
||||
"<html><head><title>503 Service Unavailable</title></head>\n"+
|
||||
"<body><h2>503 Service Unavailable</h2>\n" +
|
||||
"<p>This I2P eepsite is unavailable. It may be down or undergoing maintenance.</p>\n" +
|
||||
"</body></html>")
|
||||
.getBytes();
|
||||
"<p>This I2P website is unavailable. It may be down or undergoing maintenance.</p>\n" +
|
||||
"</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"+
|
||||
@@ -92,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"+
|
||||
@@ -105,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);
|
||||
@@ -126,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
|
||||
@@ -203,16 +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
|
||||
socket.setReadTimeout(HEADER_TIMEOUT);
|
||||
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
StringBuilder command = new StringBuilder(128);
|
||||
Map<String, List<String>> headers = readHeaders(in, 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();
|
||||
@@ -220,12 +355,24 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
(headers.containsKey("X-Forwarded-For") ||
|
||||
headers.containsKey("X-Forwarded-Server") ||
|
||||
headers.containsKey("X-Forwarded-Host"))) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Refusing inproxy access: " + peerHash.toBase64());
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Refusing inproxy access: ").append(peerHash.toBase64());
|
||||
List<String> h = headers.get("X-Forwarded-For");
|
||||
if (h != null)
|
||||
buf.append(" from: ").append(h.get(0));
|
||||
h = headers.get("X-Forwarded-Server");
|
||||
if (h != null)
|
||||
buf.append(" via: ").append(h.get(0));
|
||||
h = headers.get("X-Forwarded-Host");
|
||||
if (h != null)
|
||||
buf.append(" for: ").append(h.get(0));
|
||||
_log.warn(buf.toString());
|
||||
}
|
||||
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();
|
||||
@@ -242,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();
|
||||
@@ -302,20 +449,20 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
Runnable t;
|
||||
if (allowGZIP && useGZIP) {
|
||||
I2PAppThread req = new I2PAppThread(
|
||||
new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext(), _log),
|
||||
Thread.currentThread().getName()+".hc");
|
||||
req.start();
|
||||
t = new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext(), _log);
|
||||
} else {
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(),
|
||||
t = new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(),
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
}
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle);
|
||||
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort +
|
||||
" [" + timeToHandle +
|
||||
@@ -327,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();
|
||||
@@ -348,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();
|
||||
@@ -423,7 +570,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
|
||||
//Change headers to protect server identity
|
||||
StringBuilder command = new StringBuilder(128);
|
||||
Map<String, List<String>> headers = readHeaders(serverin, command,
|
||||
Map<String, List<String>> headers = readHeaders(null, serverin, command,
|
||||
SERVER_SKIPHEADERS, _ctx);
|
||||
String modifiedHeaders = formatHeaders(headers, command);
|
||||
compressedOut.write(modifiedHeaders.getBytes());
|
||||
@@ -439,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))
|
||||
@@ -619,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.
|
||||
*/
|
||||
@@ -659,49 +803,71 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
}
|
||||
|
||||
protected static Map<String, List<String>> readHeaders(InputStream in, StringBuilder command,
|
||||
/**
|
||||
* From I2P to server: socket non-null, in null.
|
||||
* From server to I2P: socket null, in non-null.
|
||||
*
|
||||
* @param socket if null, use in as InputStream
|
||||
* @param in if null, use socket.getInputStream() as InputStream
|
||||
* @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 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
|
||||
*/
|
||||
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);
|
||||
|
||||
// slowloris / darkloris
|
||||
long expire = ctx.clock().now() + TOTAL_HEADER_TIMEOUT;
|
||||
boolean ok = DataHelper.readLine(in, command);
|
||||
if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]");
|
||||
if (socket != null) {
|
||||
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 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, 0);
|
||||
|
||||
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);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
if (socket != null) {
|
||||
readLine(socket, buf, expire - ctx.clock().now());
|
||||
} else {
|
||||
boolean ok = DataHelper.readLine(in, buf);
|
||||
if (!ok)
|
||||
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)
|
||||
@@ -740,5 +906,77 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a line teriminated by newline, with a total read timeout.
|
||||
*
|
||||
* Warning - strips \n but not \r
|
||||
* Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded
|
||||
* Warning - not UTF-8
|
||||
*
|
||||
* @param buf output
|
||||
* @param timeout throws SocketTimeoutException immediately if zero or negative
|
||||
* @throws SocketTimeoutException if timeout is reached before newline
|
||||
* @throws EOFException if EOF is reached before newline
|
||||
* @throws LineTooLongException if too long
|
||||
* @throws IOException on other errors in the underlying stream
|
||||
* @since 0.9.19 modified from DataHelper
|
||||
*/
|
||||
private static void readLine(I2PSocket socket, StringBuilder buf, long timeout) throws IOException {
|
||||
if (timeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
long expires = System.currentTimeMillis() + timeout;
|
||||
InputStream in = socket.getInputStream();
|
||||
int c;
|
||||
int i = 0;
|
||||
socket.setReadTimeout(timeout);
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (++i > MAX_LINE_LENGTH)
|
||||
throw new LineTooLongException("Line too long - max " + MAX_LINE_LENGTH);
|
||||
if (c == '\n')
|
||||
break;
|
||||
long newTimeout = expires - System.currentTimeMillis();
|
||||
if (newTimeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
buf.append((char)c);
|
||||
if (newTimeout != timeout) {
|
||||
timeout = newTimeout;
|
||||
socket.setReadTimeout(timeout);
|
||||
}
|
||||
}
|
||||
if (c == -1) {
|
||||
if (System.currentTimeMillis() >= expires)
|
||||
throw new SocketTimeoutException();
|
||||
else
|
||||
throw new EOFException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.19
|
||||
*/
|
||||
private static class LineTooLongException extends IOException {
|
||||
public LineTooLongException(String s) {
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -136,8 +137,11 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
|
||||
DCCHelper dcc = _dccEnabled ? new DCC(s.getLocalAddress().getAddress()) : null;
|
||||
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + _clientId + " in", true);
|
||||
in.start();
|
||||
Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + _clientId + " out", true);
|
||||
out.start();
|
||||
//Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + _clientId + " out", true);
|
||||
Runnable out = new IrcOutboundFilter(s,i2ps, expectedPong, _log, dcc);
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//out.start();
|
||||
out.run();
|
||||
} catch (Exception ex) {
|
||||
// generally NoRouteToHostException
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -194,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
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Base32;
|
||||
@@ -54,23 +55,44 @@ import net.i2p.util.Log;
|
||||
* @author zzz
|
||||
*/
|
||||
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
private final byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
|
||||
private final String hostname;
|
||||
private final String method;
|
||||
private final String webircPassword;
|
||||
private final String webircSpoofIP;
|
||||
|
||||
public static final String PROP_METHOD="ircserver.method";
|
||||
public static final String PROP_METHOD_DEFAULT="user";
|
||||
public static final String PROP_CLOAK="ircserver.cloakKey";
|
||||
public static final String PROP_WEBIRC_PASSWORD="ircserver.webircPassword";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP="ircserver.webircSpoofIP";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP="ircserver.webircSpoofIP";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1";
|
||||
public static final String PROP_HOSTNAME="ircserver.fakeHostname";
|
||||
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
|
||||
private static final long HEADER_TIMEOUT = 15*1000;
|
||||
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
|
||||
private static final int MAX_LINE_LENGTH = 1024;
|
||||
|
||||
private final static byte[] ERR_UNAVAILABLE =
|
||||
(":ircserver.i2p 499 you :" +
|
||||
"This I2P IRC server is unvailable. It may be down or undergoing maintenance. " +
|
||||
private final static String ERR_UNAVAILABLE =
|
||||
":ircserver.i2p 499 you :" +
|
||||
"This I2P IRC server is unavailable. It may be down or undergoing maintenance. " +
|
||||
"Please try again later." +
|
||||
"\r\n")
|
||||
.getBytes();
|
||||
"\r\n";
|
||||
|
||||
private final static String ERR_REGISTRATION =
|
||||
":ircserver.i2p 499 you :" +
|
||||
"Bad registration." +
|
||||
"\r\n";
|
||||
|
||||
private final static String ERR_TIMEOUT =
|
||||
":ircserver.i2p 499 you :" +
|
||||
"Timeout registering." +
|
||||
"\r\n";
|
||||
|
||||
private final static String ERR_EOF =
|
||||
":ircserver.i2p 499 you :" +
|
||||
"EOF while registering." +
|
||||
"\r\n";
|
||||
|
||||
private static final String[] BAD_PROTOCOLS = {
|
||||
"GET ", "HEAD ", "POST ", "GNUTELLA CONNECT", "\023BitTorrent protocol"
|
||||
@@ -97,8 +119,8 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
// get the password for the webirc method
|
||||
this.webircPassword = opts.getProperty(PROP_WEBIRC_PASSWORD);
|
||||
|
||||
// get the spoof IP for the webirc method
|
||||
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
|
||||
// get the spoof IP for the webirc method
|
||||
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
|
||||
|
||||
// get the cloaking passphrase
|
||||
String passphrase = opts.getProperty(PROP_CLOAK);
|
||||
@@ -119,33 +141,66 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
|
||||
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
|
||||
try {
|
||||
String modifiedRegistration;
|
||||
if(!this.method.equals("webirc")) {
|
||||
// The headers _should_ be in the first packet, but
|
||||
// may not be, depending on the client-side options
|
||||
socket.setReadTimeout(HEADER_TIMEOUT);
|
||||
InputStream in = socket.getInputStream();
|
||||
modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
} else {
|
||||
StringBuffer buf = new StringBuffer("WEBIRC ");
|
||||
buf.append(this.webircPassword);
|
||||
buf.append(" cgiirc ");
|
||||
buf.append(cloakDest(socket.getPeerDestination()));
|
||||
buf.append(' ');
|
||||
buf.append(this.webircSpoofIP);
|
||||
buf.append("\r\n");
|
||||
modifiedRegistration = buf.toString();
|
||||
}
|
||||
String modifiedRegistration;
|
||||
if(!this.method.equals("webirc")) {
|
||||
// The headers _should_ be in the first packet, but
|
||||
// may not be, depending on the client-side options
|
||||
modifiedRegistration = filterRegistration(socket, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
} else {
|
||||
StringBuffer buf = new StringBuffer("WEBIRC ");
|
||||
buf.append(this.webircPassword);
|
||||
buf.append(" cgiirc ");
|
||||
buf.append(cloakDest(socket.getPeerDestination()));
|
||||
buf.append(' ');
|
||||
buf.append(this.webircSpoofIP);
|
||||
buf.append("\r\n");
|
||||
modifiedRegistration = buf.toString();
|
||||
}
|
||||
Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort());
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(),
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
} catch (RegistrationException ex) {
|
||||
try {
|
||||
// Send a response so the user doesn't just see a disconnect
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_REGISTRATION.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||
} catch (EOFException ex) {
|
||||
try {
|
||||
// Send a response so the user doesn't just see a disconnect
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_EOF.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||
} catch (SocketTimeoutException ex) {
|
||||
try {
|
||||
// Send a response so the user doesn't just see a disconnect
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_TIMEOUT.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
// Send a response so the user doesn't just see a disconnect
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_UNAVAILABLE);
|
||||
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {}
|
||||
try {
|
||||
socket.close();
|
||||
@@ -189,27 +244,35 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
return this.hostname.replace("%f", hf).replace("%c", hc);
|
||||
}
|
||||
|
||||
/** keep reading until we see USER or SERVER */
|
||||
private static String filterRegistration(InputStream in, String newHostname) throws IOException {
|
||||
/**
|
||||
* Keep reading until we see USER or SERVER.
|
||||
* This modifies the socket readTimeout, caller must save and restore.
|
||||
*
|
||||
* @throws SocketTimeoutException if timeout is reached before newline
|
||||
* @throws EOFException if EOF is reached before newline
|
||||
* @throws RegistrationException if line too long
|
||||
* @throws IOException on other errors in the underlying stream
|
||||
*/
|
||||
private static String filterRegistration(I2PSocket socket, String newHostname) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
int lineCount = 0;
|
||||
|
||||
// slowloris / darkloris
|
||||
long expire = System.currentTimeMillis() + TOTAL_HEADER_TIMEOUT;
|
||||
while (true) {
|
||||
String s = DataHelper.readLine(in);
|
||||
String s = readLine(socket, expire - System.currentTimeMillis());
|
||||
if (s == null)
|
||||
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 (lineCount == 0) {
|
||||
for (int i = 0; i < BAD_PROTOCOLS.length; i++) {
|
||||
if (s.startsWith(BAD_PROTOCOLS[i]))
|
||||
throw new IOException("Bad protocol " + BAD_PROTOCOLS[i]);
|
||||
throw new RegistrationException("Bad protocol " + BAD_PROTOCOLS[i]);
|
||||
}
|
||||
}
|
||||
if (++lineCount > 10)
|
||||
throw new IOException("Too many lines before USER or SERVER, giving up");
|
||||
throw new RegistrationException("Too many lines before USER or SERVER, giving up");
|
||||
if (System.currentTimeMillis() > expire)
|
||||
throw new IOException("Headers took too long [" + buf.toString() + "]");
|
||||
throw new SocketTimeoutException("Headers took too long");
|
||||
s = s.trim();
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Got line: " + s);
|
||||
@@ -223,12 +286,12 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
idx++;
|
||||
command = field[idx++].toUpperCase(Locale.US);
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
throw new IOException("Dropping defective message: [" + s + ']');
|
||||
throw new RegistrationException("Dropping defective message: [" + s + ']');
|
||||
}
|
||||
|
||||
if ("USER".equals(command)) {
|
||||
if (field.length < idx + 4)
|
||||
throw new IOException("Too few parameters in USER message: " + s);
|
||||
throw new RegistrationException("Too few parameters in USER message: " + s);
|
||||
// USER zzz1 hostname localhost :zzz
|
||||
// =>
|
||||
// USER zzz1 abcd1234.i2p localhost :zzz
|
||||
@@ -247,9 +310,58 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
|
||||
private final String hostname;
|
||||
private final String method;
|
||||
private final String webircPassword;
|
||||
private final String webircSpoofIP;
|
||||
/**
|
||||
* Read a line teriminated by newline, with a total read timeout.
|
||||
*
|
||||
* Warning - strips \n but not \r
|
||||
* Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded
|
||||
* Warning - not UTF-8
|
||||
*
|
||||
* @param timeout throws SocketTimeoutException immediately if zero or negative
|
||||
* @throws SocketTimeoutException if timeout is reached before newline
|
||||
* @throws EOFException if EOF is reached before newline
|
||||
* @throws RegistrationException if line too long
|
||||
* @throws IOException on other errors in the underlying stream
|
||||
* @since 0.9.19 modified from DataHelper and I2PTunnelHTTPServer
|
||||
*/
|
||||
private static String readLine(I2PSocket socket, long timeout) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
if (timeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
long expires = System.currentTimeMillis() + timeout;
|
||||
InputStream in = socket.getInputStream();
|
||||
int c;
|
||||
int i = 0;
|
||||
socket.setReadTimeout(timeout);
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (++i > MAX_LINE_LENGTH)
|
||||
throw new RegistrationException("Line too long - max " + MAX_LINE_LENGTH);
|
||||
if (c == '\n')
|
||||
break;
|
||||
long newTimeout = expires - System.currentTimeMillis();
|
||||
if (newTimeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
buf.append((char)c);
|
||||
if (newTimeout != timeout) {
|
||||
timeout = newTimeout;
|
||||
socket.setReadTimeout(timeout);
|
||||
}
|
||||
}
|
||||
if (c == -1) {
|
||||
if (System.currentTimeMillis() >= expires)
|
||||
throw new SocketTimeoutException();
|
||||
else
|
||||
throw new EOFException();
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.19
|
||||
*/
|
||||
private static class RegistrationException extends IOException {
|
||||
public RegistrationException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -62,8 +62,6 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
private long totalSent;
|
||||
private long totalReceived;
|
||||
|
||||
private static final AtomicLong __forwarderId = new AtomicLong();
|
||||
|
||||
/**
|
||||
* For use in new constructor
|
||||
* @since 0.9.14
|
||||
@@ -200,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
|
||||
@@ -208,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
|
||||
@@ -268,9 +269,10 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
in = new BufferedInputStream(in, 2*NETWORK_BUFFER_SIZE);
|
||||
StreamForwarder toI2P = new StreamForwarder(in, i2pout, true);
|
||||
StreamForwarder fromI2P = new StreamForwarder(i2pin, out, false);
|
||||
// TODO can we run one of these inline and save a thread?
|
||||
toI2P.start();
|
||||
fromI2P.start();
|
||||
// We are already a thread, so run the second one inline
|
||||
//fromI2P.start();
|
||||
fromI2P.run();
|
||||
synchronized (finishLock) {
|
||||
while (!finished) {
|
||||
finishLock.wait();
|
||||
@@ -384,7 +386,8 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
// ignore
|
||||
}
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// t2 = fromI2P now run inline
|
||||
//t2.join(30*1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,7 +429,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + '.' + __forwarderId.incrementAndGet());
|
||||
setName("StreamForwarder " + _runnerId + '.' + direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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
|
||||
|
||||
@@ -80,6 +80,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
protected I2PTunnelTask task;
|
||||
protected boolean bidir;
|
||||
private ThreadPoolExecutor _executor;
|
||||
protected volatile ThreadPoolExecutor _clientExecutor;
|
||||
private final Map<Integer, InetSocketAddress> _socketMap = new ConcurrentHashMap<Integer, InetSocketAddress>(4);
|
||||
|
||||
/** unused? port should always be specified */
|
||||
@@ -470,6 +471,16 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
if (_usePool) {
|
||||
_executor = new CustomThreadPoolExecutor(getHandlerCount(), "ServerHandler pool " + remoteHost + ':' + remotePort);
|
||||
}
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
if (tcg != null) {
|
||||
_clientExecutor = tcg.getClientExecutor();
|
||||
} 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.
|
||||
// Never shut down.
|
||||
_clientExecutor = new TunnelControllerGroup.CustomThreadPoolExecutor();
|
||||
}
|
||||
while (open) {
|
||||
try {
|
||||
I2PServerSocket ci2pss = i2pss;
|
||||
@@ -559,10 +570,25 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is run in a thread from a limited-size thread pool via Handler.run(),
|
||||
* except for a standard server (this class, no extension, as determined in getUsePool()),
|
||||
* it is run directly in the acceptor thread (see run()).
|
||||
*
|
||||
* In either case, this method and any overrides must spawn a thread and return quickly.
|
||||
* If blocking while reading the headers (as in HTTP and IRC), the thread pool
|
||||
* may be exhausted.
|
||||
*
|
||||
* See PROP_USE_POOL, DEFAULT_USE_POOL, PROP_HANDLER_COUNT, DEFAULT_HANDLER_COUNT
|
||||
*/
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
|
||||
@@ -577,7 +603,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
afterSocket = getTunnel().getContext().clock().now();
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, null,
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -42,8 +42,18 @@ public class TunnelController implements Logging {
|
||||
private final I2PTunnel _tunnel;
|
||||
private final List<String> _messages;
|
||||
private List<I2PSession> _sessions;
|
||||
private boolean _running;
|
||||
private boolean _starting;
|
||||
private volatile TunnelState _state;
|
||||
|
||||
/** @since 0.9.19 */
|
||||
private enum TunnelState {
|
||||
START_ON_LOAD,
|
||||
STARTING,
|
||||
RUNNING,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
DESTROYING,
|
||||
DESTROYED,
|
||||
}
|
||||
|
||||
public static final String KEY_BACKUP_DIR = "i2ptunnel-keyBackup";
|
||||
|
||||
@@ -104,6 +114,8 @@ public class TunnelController implements Logging {
|
||||
* the prefix should be used (and, in turn, that prefix should be stripped off
|
||||
* before being interpreted by this controller)
|
||||
*
|
||||
* Defaults in config properties are not recommended, they may or may not be honored.
|
||||
*
|
||||
* @param config original key=value mapping non-null
|
||||
* @param prefix beginning of key values that are relevant to this tunnel
|
||||
*/
|
||||
@@ -112,6 +124,7 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults in config properties are not recommended, they may or may not be honored.
|
||||
*
|
||||
* @param config original key=value mapping non-null
|
||||
* @param prefix beginning of key values that are relevant to this tunnel
|
||||
@@ -124,11 +137,10 @@ public class TunnelController implements Logging {
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelController.class);
|
||||
setConfig(config, prefix);
|
||||
_messages = new ArrayList<String>(4);
|
||||
_running = false;
|
||||
boolean keyOK = true;
|
||||
if (createKey && (getType().endsWith("server") || getPersistentClientKey()))
|
||||
keyOK = createPrivateKey();
|
||||
_starting = keyOK && getStartOnLoad();
|
||||
_state = keyOK && getStartOnLoad() ? TunnelState.START_ON_LOAD : TunnelState.STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,9 +205,11 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
|
||||
public void startTunnelBackground() {
|
||||
if (_running) return;
|
||||
_starting = true;
|
||||
new I2PAppThread(new Runnable() { public void run() { startTunnel(); } }).start();
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD)
|
||||
return;
|
||||
}
|
||||
new I2PAppThread(new Runnable() { public void run() { startTunnel(); } }, "Tunnel Starter " + getName()).start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,7 +217,17 @@ public class TunnelController implements Logging {
|
||||
*
|
||||
*/
|
||||
public void startTunnel() {
|
||||
_starting = true;
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD) {
|
||||
if (_state == TunnelState.RUNNING) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Already running");
|
||||
log("Tunnel " + getName() + " is already running");
|
||||
}
|
||||
return;
|
||||
}
|
||||
changeState(TunnelState.STARTING);
|
||||
}
|
||||
try {
|
||||
doStartTunnel();
|
||||
} catch (Exception e) {
|
||||
@@ -213,21 +237,20 @@ public class TunnelController implements Logging {
|
||||
acquire();
|
||||
stopTunnel();
|
||||
}
|
||||
_starting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException via methods in I2PTunnel
|
||||
*/
|
||||
private void doStartTunnel() {
|
||||
if (_running) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Already running");
|
||||
log("Tunnel " + getName() + " is already running");
|
||||
return;
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.STARTING)
|
||||
return;
|
||||
}
|
||||
|
||||
String type = getType();
|
||||
if ( (type == null) || (type.length() <= 0) ) {
|
||||
changeState(TunnelState.STOPPED);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot start the tunnel - no type specified");
|
||||
return;
|
||||
@@ -237,6 +260,7 @@ public class TunnelController implements Logging {
|
||||
if (type.endsWith("server") || getPersistentClientKey()) {
|
||||
boolean ok = createPrivateKey();
|
||||
if (!ok) {
|
||||
changeState(TunnelState.STOPPED);
|
||||
log("Failed to start tunnel " + getName() + " as the private key file could not be created");
|
||||
return;
|
||||
}
|
||||
@@ -268,12 +292,13 @@ public class TunnelController implements Logging {
|
||||
} else if (TYPE_STREAMR_SERVER.equals(type)) {
|
||||
startStreamrServer();
|
||||
} else {
|
||||
changeState(TunnelState.STOPPED);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot start tunnel - unknown type [" + type + "]");
|
||||
return;
|
||||
}
|
||||
acquire();
|
||||
_running = true;
|
||||
changeState(TunnelState.RUNNING);
|
||||
}
|
||||
|
||||
private void startHttpClient() {
|
||||
@@ -425,7 +450,7 @@ public class TunnelController implements Logging {
|
||||
// We use _sessions AND the tunnel sessions as
|
||||
// _sessions will be null for delay-open tunnels - see acquire().
|
||||
// We want the current sessions.
|
||||
Set<I2PSession> sessions = new HashSet(_tunnel.getSessions());
|
||||
Set<I2PSession> sessions = new HashSet<I2PSession>(_tunnel.getSessions());
|
||||
if (_sessions != null)
|
||||
sessions.addAll(_sessions);
|
||||
return sessions;
|
||||
@@ -485,6 +510,7 @@ public class TunnelController implements Logging {
|
||||
|
||||
/**
|
||||
* These are the ones stored with a prefix of "option."
|
||||
* Defaults in config properties are not honored.
|
||||
*
|
||||
* @return keys with the "option." prefix stripped, non-null
|
||||
* @since 0.9.1 Much better than getClientOptions()
|
||||
@@ -554,12 +580,17 @@ public class TunnelController implements Logging {
|
||||
* and it may have timer threads that continue running.
|
||||
*/
|
||||
public void stopTunnel() {
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.STARTING && _state != TunnelState.RUNNING)
|
||||
return;
|
||||
changeState(TunnelState.STOPPING);
|
||||
}
|
||||
// I2PTunnel removes the session in close(),
|
||||
// so save the sessions to pass to release() and TCG
|
||||
Collection<I2PSession> sessions = getAllSessions();
|
||||
_tunnel.runClose(new String[] { "forced", "all" }, this);
|
||||
release(sessions);
|
||||
_running = false;
|
||||
changeState(TunnelState.STOPPED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -569,12 +600,17 @@ public class TunnelController implements Logging {
|
||||
* @since 0.9.17
|
||||
*/
|
||||
public void destroyTunnel() {
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.RUNNING)
|
||||
return;
|
||||
changeState(TunnelState.DESTROYING);
|
||||
}
|
||||
// I2PTunnel removes the session in close(),
|
||||
// so save the sessions to pass to release() and TCG
|
||||
Collection<I2PSession> sessions = getAllSessions();
|
||||
_tunnel.runClose(new String[] { "destroy", "all" }, this);
|
||||
release(sessions);
|
||||
_running = false;
|
||||
changeState(TunnelState.DESTROYED);
|
||||
}
|
||||
|
||||
public void restartTunnel() {
|
||||
@@ -615,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());
|
||||
}
|
||||
@@ -626,26 +662,29 @@ public class TunnelController implements Logging {
|
||||
// tell i2ptunnel, who will tell the TunnelTask, who will tell the SocketManager
|
||||
setSessionOptions();
|
||||
|
||||
if (_running) {
|
||||
Collection<I2PSession> sessions = getAllSessions();
|
||||
if (sessions.isEmpty()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Running but no sessions to update");
|
||||
}
|
||||
for (I2PSession s : sessions) {
|
||||
// tell the router via the session
|
||||
if (!s.isClosed()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Session is open, updating: " + s);
|
||||
s.updateOptions(_tunnel.getClientOptions());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Session is closed, not updating: " + s);
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.RUNNING) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Not running, not updating sessions");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Not running, not updating sessions");
|
||||
}
|
||||
// Running, so check sessions
|
||||
Collection<I2PSession> sessions = getAllSessions();
|
||||
if (sessions.isEmpty()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Running but no sessions to update");
|
||||
}
|
||||
for (I2PSession s : sessions) {
|
||||
// tell the router via the session
|
||||
if (!s.isClosed()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Session is open, updating: " + s);
|
||||
s.updateOptions(_tunnel.getClientOptions());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Session is closed, not updating: " + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,19 +833,27 @@ public class TunnelController implements Logging {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean getIsRunning() { return _running; }
|
||||
public boolean getIsStarting() { return _starting; }
|
||||
public boolean getIsRunning() { return _state == TunnelState.RUNNING; }
|
||||
public boolean getIsStarting() { return _state == TunnelState.START_ON_LOAD || _state == TunnelState.STARTING; }
|
||||
|
||||
/** if running but no open sessions, we are in standby */
|
||||
public boolean getIsStandby() {
|
||||
if (!_running)
|
||||
return false;
|
||||
synchronized (this) {
|
||||
if (_state != TunnelState.RUNNING)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (I2PSession sess : _tunnel.getSessions()) {
|
||||
if (!sess.isClosed())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @since 0.9.19 */
|
||||
private synchronized void changeState(TunnelState state) {
|
||||
_state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* A text description of the tunnel.
|
||||
@@ -927,7 +974,7 @@ public class TunnelController implements Logging {
|
||||
*
|
||||
*/
|
||||
public void log(String s) {
|
||||
synchronized (this) {
|
||||
synchronized (_messages) {
|
||||
_messages.add(s);
|
||||
while (_messages.size() > 10)
|
||||
_messages.remove(0);
|
||||
@@ -942,8 +989,8 @@ public class TunnelController implements Logging {
|
||||
* @return list of messages pulled off (each is a String, earliest first)
|
||||
*/
|
||||
public List<String> clearMessages() {
|
||||
List<String> rv = null;
|
||||
synchronized (this) {
|
||||
List<String> rv;
|
||||
synchronized (_messages) {
|
||||
rv = new ArrayList<String>(_messages);
|
||||
_messages.clear();
|
||||
}
|
||||
@@ -955,6 +1002,6 @@ public class TunnelController implements Logging {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TC " + getType() + ' ' + getName() + " for " + _tunnel;
|
||||
return "TC " + getType() + ' ' + getName() + " for " + _tunnel + ' ' + _state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.*;
|
||||
@@ -36,6 +44,9 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config";
|
||||
|
||||
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";
|
||||
@@ -48,6 +59,21 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*/
|
||||
private final Map<I2PSession, Set<TunnelController>> _sessions;
|
||||
|
||||
/**
|
||||
* We keep a pool of socket handlers for all clients,
|
||||
* as there is no need for isolation on the client side.
|
||||
* Extending classes may use it for other purposes.
|
||||
*
|
||||
* May also be used by servers, carefully,
|
||||
* as there is no limit on threads.
|
||||
*/
|
||||
private ThreadPoolExecutor _executor;
|
||||
private static final AtomicLong _executorThreadCount = new AtomicLong();
|
||||
private final Object _executorLock = new Object();
|
||||
/** how long to wait before dropping an idle thread */
|
||||
private static final long HANDLER_KEEPALIVE_MS = 2*60*1000;
|
||||
|
||||
|
||||
/**
|
||||
* In I2PAppContext will instantiate if necessary and always return non-null.
|
||||
* As of 0.9.4, when in RouterContext, will return null (except in Android)
|
||||
@@ -61,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;
|
||||
@@ -75,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) {
|
||||
@@ -83,6 +111,7 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
_mgr = mgr;
|
||||
_log = _context.logManager().getLog(TunnelControllerGroup.class);
|
||||
_controllers = new ArrayList<TunnelController>();
|
||||
_controllersLock = new ReentrantReadWriteLock(true);
|
||||
if (args == null || args.length <= 0)
|
||||
_configFile = DEFAULT_CONFIG_FILE;
|
||||
else if (args.length == 1)
|
||||
@@ -121,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
|
||||
@@ -206,46 +248,80 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
if (_instance == this)
|
||||
_instance = null;
|
||||
}
|
||||
/// fixme static
|
||||
I2PTunnelClientBase.killClientExecutor();
|
||||
killClientExecutor();
|
||||
changeState(STOPPED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
Properties cfg = loadConfig(configFile);
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String type = cfg.getProperty("tunnel." + i + ".type");
|
||||
if (type == null)
|
||||
break;
|
||||
TunnelController controller = new TunnelController(cfg, "tunnel." + i + ".");
|
||||
_controllers.add(controller);
|
||||
i++;
|
||||
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)
|
||||
break;
|
||||
TunnelController controller = new TunnelController(cfg, "tunnel." + i + ".");
|
||||
_controllers.add(controller);
|
||||
i++;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
private class StartControllers implements Runnable {
|
||||
public void run() {
|
||||
synchronized(TunnelControllerGroup.this) {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
if (controller.getStartOnLoad())
|
||||
controller.startTunnel();
|
||||
_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.startTunnelBackground();
|
||||
}
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +336,7 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
public synchronized void reloadControllers() {
|
||||
unloadControllers();
|
||||
loadControllers(_configFile);
|
||||
startControllers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,19 +345,40 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*
|
||||
*/
|
||||
public synchronized void unloadControllers() {
|
||||
destroyAllControllers();
|
||||
_controllers.clear();
|
||||
synchronized (_controllersLoadedLock) {
|
||||
if (!_controllersLoaded)
|
||||
return;
|
||||
}
|
||||
|
||||
_controllersLock.writeLock().lock();
|
||||
try {
|
||||
destroyAllControllers();
|
||||
_controllers.clear();
|
||||
} finally {
|
||||
_controllersLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
synchronized (_controllersLoadedLock) {
|
||||
_controllersLoaded = false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("All controllers stopped and unloaded");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the given tunnel to the set of known controllers (but dont add it to
|
||||
* a config file or start it or anything)
|
||||
*
|
||||
*/
|
||||
public synchronized void addController(TunnelController controller) { _controllers.add(controller); }
|
||||
|
||||
public synchronized void addController(TunnelController controller) {
|
||||
_controllersLock.writeLock().lock();
|
||||
try {
|
||||
_controllers.add(controller);
|
||||
} finally {
|
||||
_controllersLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop and remove the given tunnel
|
||||
*
|
||||
@@ -290,7 +388,12 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
if (controller == null) return new ArrayList<String>();
|
||||
controller.stopTunnel();
|
||||
List<String> msgs = controller.clearMessages();
|
||||
_controllers.remove(controller);
|
||||
_controllersLock.writeLock().lock();
|
||||
try {
|
||||
_controllers.remove(controller);
|
||||
} finally {
|
||||
_controllersLock.writeLock().unlock();
|
||||
}
|
||||
msgs.add("Tunnel " + controller.getName() + " removed");
|
||||
return msgs;
|
||||
}
|
||||
@@ -302,13 +405,18 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*/
|
||||
public synchronized List<String> stopAllControllers() {
|
||||
List<String> msgs = new ArrayList<String>();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.stopTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.stopTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers stopped");
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers stopped");
|
||||
return msgs;
|
||||
}
|
||||
|
||||
@@ -334,14 +442,19 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*/
|
||||
public synchronized List<String> startAllControllers() {
|
||||
List<String> msgs = new ArrayList<String>();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.startTunnelBackground();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.startTunnelBackground();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers started");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers started");
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
@@ -352,13 +465,18 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*/
|
||||
public synchronized List<String> restartAllControllers() {
|
||||
List<String> msgs = new ArrayList<String>();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.restartTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.restartTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers restarted");
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers restarted");
|
||||
return msgs;
|
||||
}
|
||||
|
||||
@@ -367,11 +485,16 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*
|
||||
* @return list of messages the tunnels have generated
|
||||
*/
|
||||
public synchronized List<String> clearAllMessages() {
|
||||
public List<String> clearAllMessages() {
|
||||
List<String> msgs = new ArrayList<String>();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
msgs.addAll(controller.clearMessages());
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
@@ -398,10 +521,15 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
parent.mkdirs();
|
||||
|
||||
Properties map = new OrderedProperties();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
Properties cur = controller.getConfig("tunnel." + i + ".");
|
||||
map.putAll(cur);
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = _controllers.get(i);
|
||||
Properties cur = controller.getConfig("tunnel." + i + ".");
|
||||
map.putAll(cur);
|
||||
}
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
|
||||
DataHelper.storeProps(map, cfgFile);
|
||||
@@ -435,12 +563,26 @@ 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 synchronized List<TunnelController> getControllers() {
|
||||
return new ArrayList<TunnelController>(_controllers);
|
||||
public List<TunnelController> getControllers() {
|
||||
synchronized (_controllersLoadedLock) {
|
||||
if (!_controllersLoaded)
|
||||
loadControllers(_configFile);
|
||||
}
|
||||
|
||||
_controllersLock.readLock().lock();
|
||||
try {
|
||||
return new ArrayList<TunnelController>(_controllers);
|
||||
} finally {
|
||||
_controllersLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -500,4 +642,59 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-null
|
||||
* @since 0.8.8 Moved from I2PTunnelClientBase in 0.9.18
|
||||
*/
|
||||
ThreadPoolExecutor getClientExecutor() {
|
||||
synchronized (_executorLock) {
|
||||
if (_executor == null)
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
}
|
||||
return _executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.8 Moved from I2PTunnelClientBase in 0.9.18
|
||||
*/
|
||||
private void killClientExecutor() {
|
||||
synchronized (_executorLock) {
|
||||
if (_executor != null) {
|
||||
_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
|
||||
_executor.shutdownNow();
|
||||
_executor = null;
|
||||
}
|
||||
}
|
||||
// kill the shared client, so that on restart in android
|
||||
// we won't latch onto the old one
|
||||
I2PTunnelClientBase.killSharedClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not really needed for now but in case we want to add some hooks like afterExecute().
|
||||
* Package private for fallback in case TCG.getInstance() is null, never instantiated
|
||||
* but a plugin still needs it... should be rare.
|
||||
*
|
||||
* @since 0.9.18 Moved from I2PTunnelClientBase
|
||||
*/
|
||||
static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
|
||||
public CustomThreadPoolExecutor() {
|
||||
super(0, Integer.MAX_VALUE, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS,
|
||||
new SynchronousQueue<Runnable>(), new CustomThreadFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Just to set the name and set Daemon
|
||||
* @since 0.9.18 Moved from I2PTunnelClientBase
|
||||
*/
|
||||
private static class CustomThreadFactory implements ThreadFactory {
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread rv = Executors.defaultThreadFactory().newThread(r);
|
||||
rv.setName("I2PTunnel Client Runner " + _executorThreadCount.incrementAndGet());
|
||||
rv.setDaemon(true);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +77,9 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase {
|
||||
try {
|
||||
i2ps = createI2PSocket(dest, opts);
|
||||
Thread t = new Runner(s, i2ps);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (Exception ex) {
|
||||
_log.error("Could not make DCC connection to " + _dest + ':' + _remotePort, ex);
|
||||
closeSocket(s);
|
||||
|
||||
@@ -111,7 +111,9 @@ public class I2PTunnelDCCServer extends I2PTunnelServer {
|
||||
_sockList.add(socket);
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, null, _sockList,
|
||||
(I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
local.socket = socket;
|
||||
local.expire = getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE;
|
||||
_active.put(Integer.valueOf(myPort), local);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Filters for the IRC client tunnel, and DCC handlers.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,19 +33,30 @@ import net.i2p.util.Translate;
|
||||
*/
|
||||
public abstract class LocalHTTPServer {
|
||||
|
||||
private final static byte[] ERR_404 =
|
||||
("HTTP/1.1 404 Not Found\r\n"+
|
||||
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")
|
||||
.getBytes();
|
||||
"HTTP Proxy local file not found";
|
||||
|
||||
private final static byte[] ERR_ADD =
|
||||
("HTTP/1.1 409 Bad\r\n"+
|
||||
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")
|
||||
.getBytes();
|
||||
"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.
|
||||
@@ -69,14 +80,13 @@ public abstract class LocalHTTPServer {
|
||||
* @param targetRequest decoded path only, non-null
|
||||
* @param query raw (encoded), may be null
|
||||
*/
|
||||
public static void serveLocalFile(OutputStream out, String method, String targetRequest, String query, String proxyNonce) {
|
||||
public static void serveLocalFile(OutputStream out, String method, String targetRequest,
|
||||
String query, String proxyNonce) throws IOException {
|
||||
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
||||
// a home page message for the curious...
|
||||
if (targetRequest.equals("/")) {
|
||||
try {
|
||||
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());
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
out.write(OK.getBytes("UTF-8"));
|
||||
out.flush();
|
||||
return;
|
||||
}
|
||||
if ((method.equals("GET") || method.equals("HEAD")) &&
|
||||
@@ -104,12 +114,10 @@ public abstract class LocalHTTPServer {
|
||||
else if (filename.endsWith(".jpg"))
|
||||
type = "image/jpeg";
|
||||
else type = "text/html";
|
||||
try {
|
||||
out.write("HTTP/1.1 200 OK\r\nContent-Type: ".getBytes());
|
||||
out.write(type.getBytes());
|
||||
out.write("\r\nCache-Control: max-age=86400\r\n\r\n".getBytes());
|
||||
FileUtil.readFile(filename, themesDir.getAbsolutePath(), out);
|
||||
} catch (IOException ioe) {}
|
||||
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\nConnection: close\r\nProxy-Connection: close\r\n\r\n".getBytes("UTF-8"));
|
||||
FileUtil.readFile(filename, themesDir.getAbsolutePath(), out);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -153,31 +161,24 @@ public abstract class LocalHTTPServer {
|
||||
//System.err.println("book : \"" + book + "\"");
|
||||
//System.err.println("nonce : \"" + nonce + "\"");
|
||||
if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
|
||||
try {
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Properties nsOptions = new Properties();
|
||||
nsOptions.setProperty("list", book);
|
||||
if (referer != null && referer.startsWith("http")) {
|
||||
String from = "<a href=\"" + referer + "\">" + referer + "</a>";
|
||||
nsOptions.setProperty("s", _("Added via address helper from {0}", from));
|
||||
} else {
|
||||
nsOptions.setProperty("s", _("Added via address helper"));
|
||||
}
|
||||
boolean success = ns.put(host, dest, nsOptions);
|
||||
writeRedirectPage(out, success, host, book, url);
|
||||
return;
|
||||
} catch (IOException ioe) {}
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Properties nsOptions = new Properties();
|
||||
nsOptions.setProperty("list", book);
|
||||
if (referer != null && referer.startsWith("http")) {
|
||||
String from = "<a href=\"" + referer + "\">" + referer + "</a>";
|
||||
nsOptions.setProperty("s", _("Added via address helper from {0}", from));
|
||||
} else {
|
||||
nsOptions.setProperty("s", _("Added via address helper"));
|
||||
}
|
||||
boolean success = ns.put(host, dest, nsOptions);
|
||||
writeRedirectPage(out, success, host, book, url);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
out.write(ERR_ADD);
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
out.write(ERR_ADD.getBytes("UTF-8"));
|
||||
} else {
|
||||
out.write(ERR_404.getBytes("UTF-8"));
|
||||
}
|
||||
try {
|
||||
out.write(ERR_404);
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/** @since 0.8.7 */
|
||||
@@ -193,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" +
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
A very simple HTTP server, used only for css and images on HTTP client proxy error pages.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
14
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/package.html
Normal file
14
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/package.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Implementation of preconfigured tunnels, both for clients and servers,
|
||||
and a UI for adding more and editing the configuration.
|
||||
Includes special-purpose tunnels for IRC, SOCKS, HTTP, and more.
|
||||
</p><p>
|
||||
The entry point is TunnelControllerGroup, which is started from clients.config.
|
||||
Individual tunnel configuration is in i2ptunnel.config.
|
||||
The primary API is TunnelControllerGroup and TunnelController.
|
||||
Other classes may not be maintained as a stable API.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -55,9 +55,12 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
|
||||
Thread in = new I2PAppThread(new IrcInboundFilter(clientSock, destSock, expectedPong, _log),
|
||||
"SOCKS IRC Client " + id + " in", true);
|
||||
in.start();
|
||||
Thread out = new I2PAppThread(new IrcOutboundFilter(clientSock, destSock, expectedPong, _log),
|
||||
"SOCKS IRC Client " + id + " out", true);
|
||||
out.start();
|
||||
//Thread out = new I2PAppThread(new IrcOutboundFilter(clientSock, destSock, expectedPong, _log),
|
||||
// "SOCKS IRC Client " + id + " out", true);
|
||||
Runnable out = new IrcOutboundFilter(clientSock, destSock, expectedPong, _log);
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//out.start();
|
||||
out.run();
|
||||
} catch (SOCKSException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error from SOCKS connection", e);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -56,7 +53,9 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||
Thread t = new I2PTunnelRunner(clientSock, destSock, sockLock, null, null, mySockets,
|
||||
(I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (SOCKSException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error from SOCKS connection", e);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user