Selected and modified portions of patch from drz3d:

- Overhaul front-end to use full html output and a default light theme
 - Enable GeoIP lookups for nodes to permit display of flags
 - Add country code lookups for nodes and display on tooltip
 - Mitigate logging output issues caused by html output
 - Set useMicroDescriptors to FALSE so additional node information can
   be presented in the UI, if enough memory available
 - Allow 2nd parameter in extra-info-digest when pulling full descriptors
 - Display platform, observed bandwidth and uptime on circuit node tooltips
 - Add hints and notes to config section, and include missing options
 - Change maxCircuitDirtiness to Tor default of 10 minutes

The modifications to the Orchid source code are released under the same license as the parent application.
--
dr|z3d - z3d@mail.i2p

Thanks to George Hodan for the orchid image
http://www.publicdomainpictures.net/view-image.php?image=35307
See LICENSE-FatCowIcons.txt & LICENSE-Fugue-Icons.txt in i2p/licenses/ for icon licences

Conversion from new MaxMind format to v.1 GeoIP.dat format courtesy of:
https://github.com/sherpya/geolite2legacy
Usage:
wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
./geolite2legacy.py -i GeoLite2-Country-CSV.zip -f geoname2fips.csv -o GeoIP.dat
For GeoIP licensing information, See the MaxMind license in the I2P application directory:
i2p/licenses/LICENSE-GeoIP.txt
This commit is contained in:
zzz
2019-08-17 14:35:17 +00:00
parent d0ef965cd9
commit c4038916c4
64 changed files with 1621 additions and 168 deletions

View File

@ -1,3 +1,15 @@
* 2019-08-xx 1.2.2-0.6
- Overhaul front-end to use full html output and a default light theme
- Enable GeoIP lookups for nodes to permit display of flags
- Add country code lookups for nodes and display on tooltip
- Mitigate logging output issues caused by html output
- Set useMicroDescriptors to FALSE so additional node information can
be presented in the UI, if enough memory available
- Allow 2nd parameter in extra-info-digest when pulling full descriptors
- Display platform, observed bandwidth and uptime on circuit node tooltips
- Add hints and notes to config section, and include missing options
- Change maxCircuitDirtiness to Tor default of 10 minutes
* 2019-06-24 1.2.2-0.5
- Remove unused XMLRPC Transport and related libs
- Reduce max descriptor age

View File

@ -13,9 +13,17 @@
<buildnumber file="scripts/build.number" />
<property name="release.number" value="1.2.2-0.5" />
<!-- add the GeoIP.dat file & README.txt -->
<copy file="geoip/GeoIP.dat" todir="plugin/geoip/" overwrite="true" />
<copy file="geoip/README.txt" todir="plugin/geoip/" overwrite="true" />
<!-- add info about maxmind license -->
<copy file="geoip/license.txt" tofile="plugin/licenses/LICENSE-GeoIP.txt" overwrite="true" />
<!-- make the update xpi2p -->
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
<copy file="README.txt" todir="plugin/" overwrite="true" />
<copy file="CHANGES.txt" todir="plugin/" overwrite="true" />
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="update-only=true" />
@ -61,6 +69,11 @@
<target name="clean" >
<ant dir="src" target="clean" />
<defaultexcludes remove="**/*~"/>
<delete>
<fileset dir="." includes="*/*.~ **/*.*~ *.*~" />
</delete>
<delete file="plugin/i2ptunnel.config" />
<delete file="plugin/orchid.config" />
<delete file="plugin/plugin.config" />
@ -71,6 +84,7 @@
<delete file="plugin/console/webapps/orchid.war.pack" />
<delete file="plugin/LICENSE.txt" />
<delete file="plugin/README.txt" />
<delete file="plugin/CHANGES.txt" />
<delete file="orchid.xpi2p" />
<delete file="orchid-update.xpi2p" />
<delete file="orchid.su3" />

BIN
geoip/GeoIP.dat Normal file

Binary file not shown.

8
geoip/README.txt Normal file
View File

@ -0,0 +1,8 @@
Conversion from new MaxMind format to v.1 GeoIP.dat format courtesy of:
https://github.com/sherpya/geolite2legacy
Usage:
wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
./geolite2legacy.py -i GeoLite2-Country-CSV.zip -f geoname2fips.csv -o GeoIP.dat

2
geoip/license.txt Normal file
View File

@ -0,0 +1,2 @@
For licensing information, See the MaxMind license in the I2P application directory:
i2p/licenses/LICENSE-GeoIP.txt

11
resources/ajaxRefresh.js Normal file
View File

@ -0,0 +1,11 @@
setInterval(function() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/orchid/?" + new Date().getTime(), true);
xhr.responseType = "text";
xhr.onreadystatechange = function () {
if (xhr.readyState==4 && xhr.status==200) {
document.getElementById("orchid").innerHTML = xhr.responseText;
}
}
xhr.send();
}, 15000);

13
resources/collapse.css Normal file
View File

@ -0,0 +1,13 @@
#configuration {
display: none !important;
}
#expand {
display: inline-block !important;
z-index: 100 !important;
}
#collapse {
display: none !important;
z-index: -1 !important;
}

13
resources/expand.css Normal file
View File

@ -0,0 +1,13 @@
#configuration {
display: table !important;
}
#expand {
display: none !important;
z-index: -1 !important;
}
#collapse {
display: inline-block !important;
z-index: 100 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
resources/images/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

BIN
resources/images/exit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
resources/images/expand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
resources/images/globe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
resources/images/node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
resources/images/orchid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
resources/images/rx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/images/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

BIN
resources/images/target.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

BIN
resources/images/tick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

BIN
resources/images/tile2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/images/tx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

966
resources/orchid.css Normal file
View File

@ -0,0 +1,966 @@
/* I2P Orchid Plugin Theme by dr|z3d 2019 */
/* Thanks to George Hodan for the orchid image */
/* http://www.publicdomainpictures.net/view-image.php?image=35307 */
/* See LICENSE-FatCowIcons.txt & LICENSE-Fugue-Icons.txt in i2p/licenses/ for icon licences */
body {
margin: 10px;
padding: 0;
min-width: 860px;
color: #41465f;
background: url(images/tile2.png) fixed #a4a4cb;
}
body, table {
font-family: "Droid Sans", "Noto Sans", Ubuntu, "Segoe UI", Verdana, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
html {
scrollbar-color: rgba(16,16,48,.3) rgba(0,0,0,0);
}
html:hover {
scrollbar-color: rgba(16,16,48,.4) rgba(0,0,0,0);
}
::selection {
background: #77f;
color: #fff;
text-shadow: none;
}
hr, .hidden {
display: none;
}
a:link, .node a:visited, .nickname {
color: #3b6bbf;
text-decoration: none;
outline: none;
}
a:visited {
color: #2c4e8f;
text-decoration: none;
}
a:hover, a:focus {
color: #f60;
}
a:active {
color: #f30;
}
#container {
margin: 15px 5px;
padding: 8px;
max-width: 1920px;
border: 1px solid #447;
box-shadow: inset 0 0 0 1px #bbf;
background: #fff;
background: linear-gradient(to right, #fff, #efefff, #fff);
/* background: repeating-linear-gradient(45deg, rgba(255,255,255,.5) 1px, rgba(221, 221, 255, .1) 2px, rgba(255,255,255,.1) 2px), repeating-linear-gradient(135deg, rgba(255,255,255,1), rgba(221, 221, 255, .3) 1px, #fff 2px) #fafaff !important;
background-blend-mode: multiply, normal !important;*/
filter: drop-shadow(0 1px 1px #97a2ce);
}
code {
padding: 0 2px;
font-family: "Droid Sans Mono", "Noto Mono", "DejaVu Sans Mono", "Lucida Console", monospace;
color: #373;
}
.notice code {
font-weight: bold;
color: #fff;
border-radius: 2px;
background: #99a;
-moz-user-select: all;
-webkit-user-select: all;
user-select: all;
}
#refresh {
margin: -1px -2px 0 0;
float: right;
}
#refresh a, #refresh a:visited {
margin: -2px 0 !important;
padding: 5px 5px 5px 20px;
display: inline-block;
line-height: 100%;
letter-spacing: normal;
text-decoration: none;
font-weight: normal;
text-transform: none;
font-size: 9.5pt;
color: #41465f !important;
border: 1px solid #97a2ce;
border-radius: 2px;
box-shadow: inset 0 0 0 1px #fff;
background: #fff url(images/refresh.png) 4px center no-repeat;
background: url(images/refresh.png) 4px center no-repeat, linear-gradient(to bottom, #fff, #eef);
background-size: 14px 14px, 100% 100% !important;
}
#refresh a:hover, #refresh a:focus {
border: 1px solid #f60;
background: #eee url(images/refresh.png) 4px center no-repeat;
background: url(images/refresh.png) 4px center no-repeat, linear-gradient(to bottom, #ddd, #fff);
filter: drop-shadow(0 0 1px #89f);
}
#refresh a:active {
box-shadow: inset 3px 3px 3px #999;
box-shadow: inset 0 0 0 1px #fff, inset 3px 3px 3px #999;
}
table {
width: 100%;
border-collapse: collapse;
border: 1px solid #7778bf;
background: #f8f8ff;
}
tr {
border: 1px solid #7778bf;
}
th#circuitsatus, #configtitle {
font-size: 13pt;
}
tr.subtitle th {
padding: 6px 8px;
letter-spacing: normal;
text-transform: none;
font-size: 10.5pt;
background: #f6f6ff;
background: linear-gradient(to bottom, #fdfdff, #f0f0ff);
}
#status th, #status td {
text-align: center;
}
#status th, #status td {
width: 25% !important;
}
#status td {
padding: 9px 8px;
border: 1px solid #7778bf;
box-shadow: inset 0 0 0 1px #fff;
background: #f2f2ff;
}
#status td:first-child {
text-transform: lowercase;
}
#status td:first-child::first-letter {
text-transform: uppercase;
}
#status td::before {
display: none;
}
#starting, #running, #registered, #notregistered {
font-size: 0;
}
#starting::before, #running::before, #registered::before, #notregistered::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
background: url(images/starting.png) center center no-repeat;
background-size: 16px 16px;
animation: spin linear 3s forwards infinite;
}
#running::before, #registered::before {
background: url(images/tick.png) center center no-repeat;
background-size: 16px 16px;
animation: none;
}
#notregistered::before {
background: url(images/cross.png) center center no-repeat;
background-size: 16px 16px;
animation: none;
}
@keyframes spin {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
table table {
box-shadow: 0 0 1px 0 #bbc;
}
table table tr:not(.stream):nth-child(odd), #configuration tr:not(.stream):nth-child(odd) {
background: #f2f2ff;
background: repeating-linear-gradient(45deg, rgba(255,255,255,.5) 2px, rgba(220, 220, 255, .3) 3px, #fafaff 5px), #fafaff;
}
table table tr:not(.stream):nth-child(even), #configuration tr:not(.stream):nth-child(even) {
background: #ededff;
background: repeating-linear-gradient(135deg, rgba(252,252,255,.5) 2px, rgba(240, 240, 255, .3) 3px, #fafaff 5px) #f0f0ff;
}
table table:not(#status) tr:nth-child(n+3):hover, #configuration tr:nth-child(n+3):hover td, #status td:hover {
color: #292d3d;
background-color: #ffe;
}
td {
padding: 8px;
}
table table td::before {
content: "";
display: inline-block;
min-height: 20px;
vertical-align: middle;
}
#ports td::before {
min-height: 24px;
}
table table td, #configuration table td {
padding: 5px 8px;
}
td.notice, tr:hover td.notice {
padding: 15px 8px 15px 38px !important;
color: #222;
box-shadow: inset 0 0 0 1px #fff, inset 0 0 1px 1px #bbf;
background: #f8f8ff url(images/infohelp.png) 8px center no-repeat !important;
background-size: 24px 24px !important;
}
th, #configtitle {
padding: 6px 8px;
letter-spacing: 0.08em;
text-transform: uppercase;
text-align: left;
font-size: 11pt;
background: #fff;
background: linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
}
th[colspan="2"] {
width: 50%;
}
#circuitstatus, #configtitle {
font-size: 12.5pt;
}
th#title {
padding: 10px 8px;
font-size: 16pt;
letter-spacing: 0.08em !important;
line-height: 110%;
text-shadow: 0 1px 1px #fff,0 -1px 1px #e2e2ff, 0 2px 1px #ddf;
background: url(images/orchid.png) right top no-repeat, linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
background-size: auto 120%, 100% 100%;
}
#circuitstatus {
background: url(images/circuits.png) 8px center no-repeat, linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
padding: 8px 8px 8px 37px;
background-size: 24px 24px, 100% 100%;
}
#circuitstatus, #configtitle, #circuitmonitor tr:first-child, #circuitstatus tr:first-child, #conncache tr:first-child, #ports tr:first-child {
text-shadow: 0 1px 2px #fff, 0 1px 1px #ccf;
}
#circuitmon tr:nth-child(2) th:nth-child(n+3), #circuitmon td:nth-child(n+3),
#conncache th:nth-child(n+2), #conncache td:nth-child(n+2) {
text-align: center;
}
#circuitmon td:nth-child(2), #circuitmon td:nth-child(3) {
padding-left: 10px;
}
#circuitmon td:nth-child(3) .nowrap {
margin-left: 28px;
min-width: 80px;
display: inline-block;
text-align: left;
}
#circuitmon .circuit td:nth-child(3) i {
padding: 1px 8px;
display: inline-block;
min-width: 120px;
text-align: center;
border: 1px solid #ddf;
box-shadow: inset 0 0 0 1px #fff;
background: repeating-linear-gradient(135deg, rgba(238, 238, 255,.7) 1px, rgba(238, 238, 255, .7) 5px, rgba(221, 221, 255, .7) 6px, rgba(221, 221, 255, .7) 11px);
background-size: 800% 800%;
animation: progress 120s linear infinite both;
}
.stream .target {
margin: 0 1px;
}
#circuitmon td:last-child, .stream td:nth-child(2) {
white-space: nowrap;
}
#conncache tr:first-child th, #circuitstatus tr:first-child th, #ports tr:first-child th, #circuitmon tr:first-child th {
font-size: 11.5pt;
}
#conncache .nowrap, #ports .nowrap {
text-align: right;
}
#conncache .nowrap {
min-width: 30px;
}
#conncache td:first-child {
white-space: nowrap;
}
#conncache td:nth-child(2) .nowrap {
min-width: 20px;
}
#ports .nowrap, #conncache td:nth-child(n+4) .nowrap {
min-width: 60px;
}
#ports .subtitle th:last-child, #ports td:last-child {
text-align: center;
}
#ports td:last-child .nowrap {
min-width: 120px;
text-align: right;
}
.circuitcount {
margin-left: 6px;
padding: 3px 4px 2px;
min-width: 24px;
display: inline-block;
vertical-align: middle;
font-weight: bold;
text-align: center;
text-shadow: 0 1px 1px #fff;
border-radius: 2px;
background: #dfdfff;
}
.circuitcontainer {
will-change: transform;
}
.nodecontainer {
position: relative;
vertical-align: middle;
}
.node {
margin: 1px 0;
padding: 2px 8px 2px 0;
position: relative;
display: inline-block;
vertical-align: middle;
border: 1px solid #bbf;
border-radius: 2px;
box-shadow: inset 0 0 0 1px #fff, 0 0 0 1px #eef;
background: linear-gradient(to bottom, #f8f8ff, #e2e2ff);
-moz-user-select: all;
-webkit-user-select: all;
user-select: all;
cursor: text;
}
.node .hidden {
font-size: 0;
display: inline;
}
.node:hover {
border-right: 10px solid #bbf;
}
.node:active {
border-color: #f30;
box-shadow: none;
cursor: copy;
}
.nickname a::selection {
background: rgba(0,0,0,0) !important;
color: #3b6bbf !important;
}
.node:active .nickname a::selection {
color: #fff !important;
}
.node:active .nickname {
background-color: #f30;
box-shadow: none;
}
.node:active .flag {
border-color: #f30;
box-shadow: none;
}
a:hover .nickname::after, .flag::after {
color: #41465f;
}
a:visited:hover .nickname, a:visited:focus .nickname {
color: #f60 !important;
}
a:visited:active .nickname {
color: #f30 !important;
}
.node img {
vertical-align: middle;
margin: -1px 4px 0 0;
}
.node b {
font-size: 0;
}
.flag {
margin-right: 4px;
margin: -3px 4px -3px 0;
padding: 2px 1px 3px 5px;
display: inline-block;
vertical-align: middle;
border-right: 1px solid #bbf;
box-shadow: inset 0 0 0 1px #fff;
cursor: help;
-mozilla-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.flag img {
padding-bottom: 1px
}
.nickname::after, .flag::after {
content: attr(data-ipv4);
padding: 2px 3px 2px 15px;
display: none;
position: absolute;
z-index: 100;
top: -20px;
left: -10px;
white-space: nowrap;
text-align: center;
text-shadow: 0 1px 1px #fff;
border: 1px solid #373;
border-radius: 2px;
box-shadow: inset 0 0 0 1px #fff, 0 0 1px 1px rgba(192,192,192,.3);
background: url(images/node.png) left -1px center no-repeat, #ada;
background: url(images/node.png) left center no-repeat, linear-gradient(to bottom, #efe, #ded);
background-size: 16px 16px, 100% 100%;
opacity: 0;
}
.nickname::after {
padding-left: 17px;
color: #41465f;
}
.nickname.unknown::after {
display: none !important;
}
#circuitmon .nickname::after {
left: -70px;
white-space: nowrap;
text-align: left;
}
.flag::after {
content: attr(data-country);
padding-left: 18px;
z-index: 101;
background: url(images/globe.png) left 4px center no-repeat, #ada;
background: url(images/globe.png) left 4px center no-repeat, linear-gradient(to bottom, #efe, #ded);
background-size: 12px 12px, 100% 100% !important;
}
.nickname {
margin: -2px -8px -2px -4px;
padding: 2px 6px 3px 5px;
display: inline-block;
box-shadow: inset 0 0 0 1px #fff;
width: 100px;
overflow-x: hidden;
text-overflow: ellipsis;
text-align: center;
vertical-align: middle;
}
.node:hover .nickname {
width: auto;
min-width: 100px;
}
#conncache .nickname {
margin-right: -8px;
}
.nickname:hover::after, .flag:hover::after {
display: inline-block;
width: auto;
opacity: 1;
}
#circuitmon .nodecontainer::after {
content: "";
display: inline-block;
position: absolute;
top: calc(50%);
right: -4px !important;
height: 1px;
width: 4px;
vertical-align: middle;
background: #bbf;
z-index: 10;
}
#circuitmon .nodecontainer:last-of-type::after {
display: none !important;
}
.stream {
background-color: #f8fff8;
}
.stream td:first-child, .stream:hover td:first-child, .circuit td:first-child, .circuit:hover td:first-child {
padding-left: 26px !important;
background-image: url(images/tag_stream.png) !important;
background-size: 14px 14px !important;
background-position: 8px center !important;
background-repeat: no-repeat !important;
}
.circuit td:first-child, .circuit:hover td:first-child {
background-image: url(images/tag.png) !important;
}
.stream td:last-child {
text-align: center;
}
.stream td:last-child i {
margin-left: 1px;
padding: 1px 16px;
min-width: 106px;
display: inline-block;
text-align: center;
color: #4a4;
text-shadow: 0 1px 1px #fff;
border: 1px solid #aca;
box-shadow: inset 0 0 0 1px #fff;
background: repeating-linear-gradient(135deg, rgba(240, 255, 240, .7) 1px, rgba(240, 255, 240, .7) 6px, rgba(210, 240, 210,.7) 7px, rgba(210, 240, 210,.7) 11px);
background-size: 800% 800%;
animation: progress 120s linear infinite both;
}
.stream:hover td:last-child i {
color: #bb6;
border: 1px solid #cca;
background: repeating-linear-gradient(135deg, rgba(255, 255, 240, .7) 1px, rgba(255, 255, 240, .7) 6px, rgba(240, 240, 220,.7) 7px, rgba(240, 240, 220,.7) 11px);
background-size: 800% 800%;
animation: progress 120s linear infinite both;
}
@keyframes progress {
from {
background-position: 200%
}
to {
background-position: 0;
}
}
.streamID {
display: none;
}
.nowrap {
display: inline-block;
white-space: nowrap;
vertical-align: middle;
}
.stream .nowrap {
min-width: 60px;
text-align: center;
}
.stream .nowrap:first-of-type {
padding-right: 20px;
text-align: left;
}
.stream .nowrap:last-of-type {
padding-left: 20px;
text-align: left;
}
.internal, .exit, .directory, .hiddenservice {
padding-left: 20px;
display: inline-block;
min-width: 120px;
vertical-align: middle;
text-align: left;
background: url(images/internal.png) left center no-repeat;
background-size: 16px 16px !important;
}
.exit {
background: url(images/exit.png) left center no-repeat;
}
.directory {
background: url(images/directory.png) left center no-repeat;
}
.hiddenservice {
background: url(images/hiddenservice.png) left center no-repeat;
}
.rx::before, .tx::before, .target::before {
content: "";
display: inline-block;
width: 14px;
height: 16px;
vertical-align: top;
}
.tx::before {
background: url(images/tx.png) left center no-repeat;
background-size: 14px 14px;
}
.rx::before {
background: url(images/rx.png) left center no-repeat;
background-size: 14px 14px;
}
.target::before {
margin-top: -6px;
width: 15px;
height: 16px;
vertical-align: middle;
background: url(images/target.png) left top no-repeat;
background-size: 14px 14px;
}
.rx, .tx, .target {
font-size: 0;
}
#configsection, #configsection td {
border: none;
}
#configsection td {
padding: 0;
}
#configsection table td {
padding: 5px 8px;
}
#configuration {
position: relative;
border: none;
}
#configuration tr {
border-left: none !important;
border-right: none !important;
}
#configuration td:first-child {
border-left: none !important;
}
#configuration td:last-child {
border-right: none !important;
}
#configuration tr:last-child, #configuration tr:last-child td {
border-bottom: none !important;
}
#configuration th {
width: 25%;
text-transform: none;
letter-spacing: normal;
}
#configuration tr:nth-child(n+3) td:first-child {
font-weight: bold;
}
#configuration tr:hover td {
box-shadow: none;
}
#configuration td:first-child {
border-left: none !important;
}
#configuration td:last-child {
border-right: none !important;
line-height: 1.1;
}
#configtitle {
margin: -1px 0;
padding: 8px;
padding-left: 37px;
width: calc(100% - 47px);
display: inline-block;
position: relative;
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 12.5pt;
font-weight: bold;
border: 1px solid #7778bf;
border-left: none;
border-right: none;
background: url(images/configure.png) 8px center no-repeat, linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
background-size: 24px 24px, 100% 100% !important;
}
#expand, #collapse {
display: inline-block !important;
position: absolute;
top: calc(50% - 18px);
right: -1px;
font-size: 0;
border-left: 1px solid #7778bf;
}
#collapse {
display: none !important;
}
#expand img, #collapse img {
padding: 13px;
width: 10px;
height: 10px;
display: inline-block;
vertical-align: middle;
}
#expand:hover img, #collapse:hover img {
filter: drop-shadow(0 0 1px #f60);
}
#configuration td:nth-child(2) code {
color: #050;
line-height: 115%;
}
#configuration td:nth-child(2) code, #configuration td:nth-child(2) .nowrap {
display: inline-block;
vertical-align: middle;
}
#configuration tr:hover td:nth-child(2) code {
color: #070;
}
#configuration tr:hover td:nth-child(2) code::selection {
color: #fff;
background: #070;
}
#configuration .nowrap {
font-style: italic;
color: #41465f;
}
#configuration i {
margin-right: 1px;
}
.stream, .circuit {
border-top: 1px solid #ddf;
border-bottom: 1px solid #ddf;
border-left: 1px solid #7778bf;
border-right: 1px solid #7778bf;
}
.circuit, .stream, #conncache tr, #ports tr, #configuration tr {
border-top: 1px solid #ddf;
border-bottom: 1px solid #ddf;
}
tr:first-child td, tr:last-child, #configuration tr:last-child, #conncache tr:last-child, #ports tr:last-child {
border-bottom: 1px solid #7778bf;
}
#configuration tr:first-child, #configuration tr:nth-child(2),
#conncache tr:first-child, #conncache tr:nth-child(2),
#ports tr:first-child, #ports tr:nth-child(2) {
border-top: 1px solid #7778bf;
border-bottom: 1px solid #7778bf;
}
#conncache tr:nth-child(2), #conncache tr:nth-child(2) th {
border-bottom: 1px solid #7778bf !important;
}
#conncache tr:nth-child(3), #conncache tr:nth-child(3) td {
border-top: 1px solid #7778bf !important;
}
td, #configuration td {
border: 1px inset #dde;
border-left-style: solid;
border-right-style: solid;
box-shadow: inset 0 0 0 1px #fff !important;
}
.stream td, .stream:hover td, .circuit:hover td, #conncache tr:nth-child(n+3):hover td, #ports tr:nth-child(n+3):hover td, #configuration tr:nth-child(n+3):hover td {
border: none;
box-shadow: none !important;
}
td:first-child {
border-left: 1px solid #7778bf !important;
}
td:last-child {
border-right: 1px solid #7778bf !important;
}
.stream td:first-child {
border-right: 1px solid #f8fff8 !important;
}
.stream td:last-child {
border-left: 1px solid #f8fff8 !important;
}
.stream:hover td:first-child {
border-right: 1px solid #ffe !important;
}
.stream:hover td:last-child {
border-left: 1px solid #ffe !important;
}
table table tr:hover td {
background-color: #ffe;
}
@media screen and (max-width: 1480px) {
#circuitmon th, #circuitmon td {
width: auto !important;
}
#circuitmon tr:nth-child(2) th:last-child {
width: 60% !important;
}
#circuitmon th, #conncache th {
white-space: nowrap;
}
}
@media screen and (max-width: 1000px) {
body {
margin: 3px;
}
#container {
margin: 0;
}
body, table {
font-size: 9.5pt !important;
}
#title {
font-size: 14pt !important;
}
th#circuitstatus, #configtitle {
font-size: 12pt !important;
}
#conncache tr:first-child th, #circuitstatus tr:first-child th, #ports tr:first-child th, #circuitmon tr:first-child th {
font-size: 10.5pt;
}
tr.subtitle th {
font-size: 10pt;
}
.flag, .nickname, .circuitcount {
height: 16px;
}
.nickname {
width: 90px;
}
.flag img {
margin-top: 1px;
}
.circuitcount {
margin-top: 2px;
}
#refresh {
margin-top: 0;
}
.stream td:last-child i {
min-width: 96px;
}
#circuitmon .circuit td:nth-child(3) i {
min-width: 86px;
}
}
@media screen and (min-width: 1480px) {
#circuitmon th:first-child, #circuitmon th:nth-child(3) {
width: 12.5%;
}
}

48
resources/toggleConfig.js Normal file
View File

@ -0,0 +1,48 @@
var expandConfig = document.getElementById("expandConfig");
var collapseConfig = document.getElementById("collapseConfig");
var config = document.getElementById("configuration");
function hideConfig() {
if (!collapseConfig)
config.style.display = "none";
}
function showConfig() {
if (collapseConfig)
config.style.display = "block";
}
function clean() {
if (expandConfig) {
expandConfig.remove();
}
if (collapseConfig) {
collapseConfig.remove();
}
}
function expand() {
clean();
var x = document.createElement("link");
x.type="text/css";
x.rel="stylesheet";
x.href="/orchid/resources/expand.css";
x.setAttribute("id", "expandConfig");
document.head.appendChild(x);
showConfig();
}
function collapse() {
clean();
var c = document.createElement("link");
c.type="text/css";
c.rel="stylesheet";
c.href="/orchid/resources/collapse.css";
c.setAttribute("id", "collapseConfig");
document.head.appendChild(c);
hideConfig();
}
function copyText() {
document.execCommand("copy");
}

View File

@ -78,9 +78,13 @@
</target>
<target name="war" depends="precompilejsp">
<!-- add css and resources -->
<copy todir="build/war/resources" overwrite="true" >
<fileset dir="../resources" />
</copy>
<war destfile="build/orchid.war.jar" webxml="build/web.xml">
<classes dir="./build/obj" includes="**/web/*" />
<fileset dir="build/war" />
<fileset dir="./build/war" />
</war>
</target>

View File

@ -11,6 +11,9 @@ public interface Router {
String getNickname();
String getCountryCode();
IPv4Address getAddress();
String getPlatform();
String getCountryName();
int getUptime();
int getOnionPort();
int getDirectoryPort();
TorPublicKey getIdentityKey();

View File

@ -36,7 +36,7 @@ public class Tor {
private final static String implementation = "Orchid";
private final static String version = "1.0.0";
private final static String version = "1.2.2";
private final static Charset defaultCharset = createDefaultCharset();

View File

@ -73,9 +73,9 @@ public class TorClient {
return;
}
if(isStopped) {
throw new IllegalStateException("Cannot restart a TorClient instance. Create a new instance instead.");
throw new IllegalStateException("Cannot restart Orchid instance. Create a new instance instead.");
}
logger.info("Starting Orchid (version: "+ Tor.getFullVersion() +")");
logger.info("Starting Orchid... [version: " + Tor.getFullVersion() + "]");
verifyUnlimitedStrengthPolicyInstalled();
directoryDownloader.start(directory);
circuitManager.startBuildingCircuits();
@ -99,7 +99,7 @@ public class TorClient {
directory.close();
connectionCache.close();
} catch (Exception e) {
logger.log(Level.WARNING, "Unexpected exception while shutting down TorClient instance: "+ e, e);
logger.log(Level.WARNING, "Unexpected exception while shutting down Orchid instance: " + e, e);
} finally {
isStopped = true;
}
@ -208,7 +208,7 @@ public class TorClient {
throw new TorException(message);
}
} catch (NoSuchAlgorithmException e) {
logger.log(Level.SEVERE, "No AES provider found");
logger.log(Level.SEVERE, "WARNING! No AES provider found");
throw new TorException(e);
} catch (NoSuchMethodError e) {
logger.info("Skipped check for Unlimited Strength Jurisdiction Policy Files");

View File

@ -42,7 +42,7 @@ public class CircuitBuildTask implements Runnable {
circuit.notifyCircuitBuildStart();
creationRequest.choosePath();
if(logger.isLoggable(Level.FINE)) {
logger.fine("Opening a new circuit to "+ pathToString(creationRequest));
logger.fine("Selecting path for new circuit " + pathToString(creationRequest));
}
firstRouter = creationRequest.getPathElement(0);
openEntryNodeConnection(firstRouter);
@ -72,7 +72,7 @@ public class CircuitBuildTask implements Runnable {
sb.append("[");
for(Router r: ccr.getPath()) {
if(sb.length() > 1)
sb.append(",");
sb.append(" -> ");
sb.append(r.getNickname());
}
sb.append("]");

View File

@ -26,8 +26,10 @@ import com.subgraph.orchid.data.exitpolicy.ExitTarget;
public class CircuitCreationTask implements Runnable {
private final static Logger logger = Logger.getLogger(CircuitCreationTask.class.getName());
private final static int MAX_CIRCUIT_DIRTINESS = 300; // seconds
private final static int MAX_PENDING_CIRCUITS = 4;
// private final static int MAX_CIRCUIT_DIRTINESS = 300; // seconds
private final static int MAX_CIRCUIT_DIRTINESS = 600; // 10 minutes (Tor default)
// private final static int MAX_PENDING_CIRCUITS = 4;
private final static int MAX_PENDING_CIRCUITS = 32; // Tor default
private final TorConfig config;
private final Directory directory;
@ -112,7 +114,7 @@ public class CircuitCreationTask implements Runnable {
}
});
for(Circuit c: circuits) {
logger.fine("Closing idle dirty circuit: "+ c);
logger.fine("Closing idle dirty circuit \n* CircuitID: " + c);
((CircuitImpl)c).markForClose();
}
}
@ -124,7 +126,7 @@ public class CircuitCreationTask implements Runnable {
if(!directory.haveMinimumRouterInfo()) {
if(notEnoughDirectoryInformationWarningCounter % 20 == 0)
logger.info("Cannot build circuits because we don't have enough directory information");
logger.info("Not enough directory information to build circuits");
notEnoughDirectoryInformationWarningCounter++;
return;
}
@ -228,7 +230,7 @@ public class CircuitCreationTask implements Runnable {
return new CircuitBuildHandler() {
public void circuitBuildCompleted(Circuit circuit) {
logger.fine("Circuit completed to: "+ circuit);
logger.fine("Built new circuit \n* CircuitID: " + circuit);
circuitOpenedHandler(circuit);
lastNewCircuit.set(System.currentTimeMillis());
}
@ -270,7 +272,7 @@ public class CircuitCreationTask implements Runnable {
return new CircuitBuildHandler() {
public void nodeAdded(CircuitNode node) {
logger.finer("Node added to internal circuit: "+ node);
logger.finer("Added " + node + " to internal circuit");
}
public void connectionFailed(String reason) {
@ -288,7 +290,7 @@ public class CircuitCreationTask implements Runnable {
}
public void circuitBuildCompleted(Circuit circuit) {
logger.fine("Internal circuit build completed: "+ circuit);
logger.fine("Built new circuit \n* CircuitID: " + circuit);
lastNewCircuit.set(System.currentTimeMillis());
circuitManager.addCleanInternalCircuit((InternalCircuit) circuit);
}

View File

@ -35,7 +35,7 @@ public class CircuitExtender {
CircuitNode createFastTo(Router targetRouter) {
logger.fine("Creating 'fast' to "+ targetRouter);
logger.fine("Performing CREATE_FAST handshake with " + targetRouter);
final TorCreateFastKeyAgreement kex = new TorCreateFastKeyAgreement();
sendCreateFastCell(kex);
return receiveAndProcessCreateFastResponse(targetRouter, kex);
@ -50,7 +50,7 @@ public class CircuitExtender {
private CircuitNode receiveAndProcessCreateFastResponse(Router targetRouter, TorKeyAgreement kex) {
final Cell cell = circuit.receiveControlCellResponse();
if(cell == null) {
throw new TorException("Timeout building circuit waiting for CREATE_FAST response from "+ targetRouter);
throw new TorException("Circuit build timeout waiting for CREATE_FAST response from " + targetRouter);
}
return processCreatedFastCell(targetRouter, cell, kex);
@ -89,14 +89,14 @@ public class CircuitExtender {
}
private void logProtocolViolation(String sourceName, Router targetRouter) {
final String version = (targetRouter == null) ? "(none)" : targetRouter.getVersion();
final String targetName = (targetRouter == null) ? "(none)" : targetRouter.getNickname();
logger.warning("Protocol error extending circuit from ("+ sourceName +") to ("+ targetName +") [version: "+ version +"]");
final String version = (targetRouter == null) ? "n/a" : targetRouter.getVersion();
final String targetName = (targetRouter == null) ? "n/a" : targetRouter.getNickname();
logger.warning("Protocol error extending circuit from [" + sourceName + "] to [" + targetName + "] -> version: " + version);
}
private String nodeToName(CircuitNode node) {
if(node == null || node.getRouter() == null) {
return "(null)";
return "n/a";
}
final Router router = node.getRouter();
return router.getNickname();
@ -121,7 +121,7 @@ public class CircuitExtender {
if(code == Cell.ERROR_PROTOCOL) {
logProtocolViolation(source, extendTarget);
}
throw new TorException("Error from ("+ source +") while extending to ("+ extendTarget.getNickname() + "): "+ msg);
throw new TorException("Error from [" + source + "] while extending to [" + extendTarget.getNickname() + "] " + msg);
} else if(command != expectedCommand) {
final String expected = RelayCellImpl.commandToDescription(expectedCommand);
final String received = RelayCellImpl.commandToDescription(command);
@ -134,7 +134,7 @@ public class CircuitExtender {
public CircuitNode createNewNode(Router r, byte[] keyMaterial, byte[] verifyDigest) {
final CircuitNode node = CircuitNodeImpl.createNode(r, circuit.getFinalCircuitNode(), keyMaterial, verifyDigest);
logger.fine("Adding new circuit node for "+ r.getNickname());
logger.fine("Adding new circuit node [" + r.getNickname() + "]"); // we're extending the circuit with this node, not for it.
circuit.appendNode(node);
return node;

View File

@ -261,19 +261,81 @@ public abstract class CircuitImpl implements Circuit, DashboardRenderable {
protected abstract String getCircuitTypeLabel();
public String toString() {
return " Circuit ("+ getCircuitTypeLabel() + ") id="+ getCircuitId() +" state=" + status.getStateAsString() +" "+ pathToString();
return "<tr class=\"circuit\"><td title=\"Circuit ID\">" + getCircuitId() + "</td><td>" +
getCircuitTypeLabel() + "</td><td>" +
status.getStateAsString().replace("Open ", "").replace("[", "").replace("]", "") + "</td><td>" +
// "bandwidth usage stats here</td><td> -->" +
pathToString() + "</td></tr>";
}
protected String pathToString() {
final StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append("<span class=\"circuitcontainer\">");
for(CircuitNode node: nodeList) {
Router r = node.getRouter();
if(sb.length() > 1)
sb.append(",");
sb.append(node.toString());
sb.append(" <span class=\"hidden\">-> </span>");
sb.append("<span class=\"nodecontainer\"><span class=\"hidden\">[</span><span class=\"node\" onclick=\"copyText();\"><span class=\"flag\" data-country=\"");
if (r != null) {
if (r.getCountryName() != null)
sb.append(r.getCountryName() + " (" + r.getAddress() + ")");
else
sb.append(r.getAddress());
} else {
sb.append("unknown");
}
sb.append("]");
sb.append("\">");
sb.append("<img height=\"11\" width=\"16\" src=\"/flags.jsp?c=");
if (r != null && r.getCountryName() != null)
sb.append(r.getCountryCode().toLowerCase().replace("--", "a0"));
else
sb.append("a0");
sb.append("\"></span>");
if (r != null) {
String idHash = r.getIdentityHash().toString().toUpperCase();
int uptime = r.getUptime();
int bw = r.getObservedBandwidth();
sb.append("<span class=\"nickname");
if (r.getPlatform() != null || r.getUptime() > 0 || r.getObservedBandwidth() > 0)
sb.append("\" data-ipv4=\"");
if (r.getPlatform() != null)
sb.append(r.getPlatform().replace("Tor ", "").replace(" on ", " / ").replace("-alpha-dev", "-alpha"));
if (uptime > 0 && bw > 0)
sb.append(" \u2022 ");
if (bw > 0 && bw < 1048576)
sb.append((bw / 1024) + " KB/s");
else if (bw >= 1048576)
sb.append(((bw / 1024) / 1024) + " MB/s");
if (uptime > 0)
sb.append(" \u2022 Up: ");
if (uptime > 172800)
sb.append((((uptime / 60) / 60) / 24) + " days");
else if (uptime > 1440)
sb.append(((uptime / 60) / 60) + " hours");
else if (uptime > 3600)
sb.append(uptime / 60 + " minutes");
else if (uptime > 0)
sb.append(uptime + " seconds");
sb.append("\">");
sb.append("<a class=\"script\" href=\"https://metrics.torproject.org/rs.html#search/" + idHash + "\" target=\"_blank\">" + node.toString() + "</a>");
// <noscript> alternative lookup
sb.append("<noscript>");
sb.append("<a href=\"https://torstatus.blutmagie.de/router_detail.php?FP=" + idHash + "\" target=\"_blank\">" + node.toString() + "</a>");
sb.append("</noscript>");
sb.append("</span><span class=\"hidden\"> (<b>" + r.getAddress() + "</b>)</span></span><span class=\"hidden\">]</span></span>");
} else {
sb.append("<span class=\"nickname unknown\"><i>unknown</i></span>");
sb.append("</span><span class=\"hidden\">]</span></span>");
}
}
sb.append("</span>");
return sb.toString();
}

View File

@ -230,6 +230,8 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
throw new OpenFailedException("Hidden services not supported");
} else if(hostname.toLowerCase().endsWith(".exit")) {
throw new OpenFailedException(".exit addresses are not supported");
} else if (hostname.toLowerCase().endsWith(".i2p")) {
throw new OpenFailedException(".i2p addresses are not supported");
}
}
@ -336,13 +338,15 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
if((flags & DASHBOARD_CIRCUITS) == 0) {
return;
}
renderer.renderComponent(writer, flags, connectionCache);
renderer.renderComponent(writer, flags, circuitCreationTask.getCircuitPredictor());
writer.println("[Circuit Manager]");
writer.println();
writer.println("<table id=\"circuitmon\" width=\"100%\">\n<tr><th colspan=\"4\" align=\"left\">Circuit Monitor</th></tr>");
writer.println("<tr class=\"subtitle\"><th align=\"left\">Circuit ID</th><th align=\"left\">Type</th>" +
"<th align=\"left\">State</th><th align=\"left\" width=\"50%\">Participants</th></tr>");
for(Circuit c: getCircuitsByFilter(null)) {
renderer.renderComponent(writer, flags, c);
}
writer.println("</table>\n</td></tr>");
renderer.renderComponent(writer, flags, connectionCache);
renderer.renderComponent(writer, flags, circuitCreationTask.getCircuitPredictor());
}
public InternalCircuit getCleanInternalCircuit() throws InterruptedException {

View File

@ -67,9 +67,9 @@ public class CircuitNodeImpl implements CircuitNode {
public String toString() {
if(router != null) {
return "|"+ router.getNickname() + "|";
return router.getNickname();
} else {
return "|()|";
return "<i>unknown</i>";
}
}

View File

@ -79,21 +79,31 @@ public class CircuitPredictor implements DashboardRenderable {
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags)
throws IOException {
if((flags & DASHBOARD_PREDICTED_PORTS) == 0) {
return;
}
writer.println("[Predicted Ports] ");
if (getPredictedPorts().size() > 0)
writer.print("<tr>");
else
writer.print("<tr class=\".hidden\" hidden>");
writer.println("<td>\n<hr>\n<table id=\"ports\" width=\"100%\">\n<tr><th colspan=\"4\" align=\"left\">Predicted Ports</th></tr>");
writer.println("<tr class=\"subtitle\"><th colspan=\"2\" align=\"left\" width=\"50%\">Port</th><th colspan=\"2\" align=\"left\">Last Seen</th></tr>");
for(int port : portsSeen.keySet()) {
writer.write(" "+ port);
writer.write("<tr><td colspan=\"2\">" + port + "</td>");
Long lastSeen = portsSeen.get(port);
writer.write("<td colspan=\"2\">");
if(lastSeen != null) {
long now = System.currentTimeMillis();
long ms = now - lastSeen;
writer.write(" (last seen "+ TimeUnit.MINUTES.convert(ms, TimeUnit.MILLISECONDS) +" minutes ago)");
String min = "minutes";
if (ms > 59999 && ms < 120000)
min = "minute";
if (ms < 60000)
writer.write("<span class=\"nowrap\">in the last 60s</span>");
else
writer.write("<span class=\"nowrap\">" + TimeUnit.MINUTES.convert(ms, TimeUnit.MILLISECONDS) + " " + min + " ago</span>");
}
writer.println();
writer.println("</td></tr>");
}
writer.println();
}
}

View File

@ -6,7 +6,7 @@ public class CircuitStatus {
enum CircuitState {
UNCONNECTED("Unconnected"),
BUILDING("Building"),
BUILDING("<i>Building&hellip;</i>"),
FAILED("Failed"),
OPEN("Open"),
DESTROYED("Destroyed");
@ -103,9 +103,9 @@ public class CircuitStatus {
private String getDirtyString() {
if(!isDirty()) {
return "Clean";
return "<span class=\"nowrap\"><span class=\"hidden\">(</span>Clean<span class=\"hidden\">)</span></span>";
} else {
return "Dirty "+ (getMillisecondsDirty() / 1000) +"s";
return "<span class=\"nowrap\"><span class=\"hidden\">(</span>Dirty: " + (getMillisecondsDirty() / 1000) + "s<span class=\"hidden\">)</span></span>";
}
}
int nextStreamId() {

View File

@ -37,6 +37,6 @@ public class DirectoryCircuitImpl extends CircuitImpl implements DirectoryCircui
@Override
protected String getCircuitTypeLabel() {
return "Directory";
return "<span class=\"directory\">Directory</span>";
}
}

View File

@ -82,6 +82,6 @@ public class ExitCircuitImpl extends CircuitImpl implements ExitCircuit {
@Override
protected String getCircuitTypeLabel() {
return "Exit";
return "<span class=\"exit\">Exit</span>";
}
}

View File

@ -104,15 +104,15 @@ public class InternalCircuitImpl extends CircuitImpl implements InternalCircuit,
protected String getCircuitTypeLabel() {
switch(type) {
case HS_CIRCUIT:
return "Hidden Service";
return "<span class=\"hiddenservice\">Hidden Service</span>";
case HS_DIRECTORY:
return "HS Directory";
return "<span class=\"hiddenservice\">HS Directory</span>";
case HS_INTRODUCTION:
return "HS Introduction";
return "<span class=\"hiddenservice\">HS Introduction</span>";
case UNUSED:
return "Internal";
return "<span class=\"internal\">Internal</span>";
default:
return "(null)";
return "[null]";
}
}
}

View File

@ -117,7 +117,7 @@ public class StreamImpl implements Stream, DashboardRenderable {
if(isClosed)
return;
logger.fine("Closing stream "+ this);
logger.fine("Closing " + this);
isClosed = true;
inputStream.close();
@ -140,7 +140,7 @@ public class StreamImpl implements Stream, DashboardRenderable {
}
public void openDirectory(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
streamTarget = "[Directory]";
streamTarget = "Directory Service";
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN_DIR);
circuit.sendRelayCellToFinalNode(cell);
waitForRelayConnected(timeout);
@ -207,18 +207,20 @@ public class StreamImpl implements Stream, DashboardRenderable {
}
public String toString() {
return "[Stream stream_id="+ streamId + " circuit="+ circuit +" target="+ streamTarget +"]";
return "StreamID=" + streamId + " Circuit=" + circuit + " Target=" + streamTarget;
}
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
writer.print(" ");
writer.print("[Stream stream_id="+ streamId + " cid="+ circuit.getCircuitId());
writer.print("<tr class=\"stream\"><td title=\"Stream ID\">");
writer.print("<b class=\"streamID\">Stream ID: </b>" + streamId + "</td>");
// TODO: Indicate target if .onion > hiddenServiceManager.getStreamTo(hostname, port);
writer.print("<td colspan=\"2\"><b class=\"target\" title=\"Target\">Target:</b> " + streamTarget + "</td><td>");
if(relayConnectedReceived) {
writer.print(" sent="+outputStream.getBytesSent() + " recv="+ inputStream.getBytesReceived());
writer.print("<span class=\"nowrap\" title=\"Received\"><b class=\"rx\">Received:</b> " + (inputStream.getBytesReceived() / 1024) + " KB</span> " +
"<span class=\"nowrap\" title=\"Sent\"><b class=\"tx\">Sent:</b> " + (outputStream.getBytesSent() / 1024) + " KB</span>");
} else {
writer.print(" (waiting connect)");
writer.print("<i>Connecting&hellip;</i>");
}
writer.print(" target="+ streamTarget);
writer.println("]");
writer.println("</td></tr>");
}
}

View File

@ -177,39 +177,39 @@ public class CellImpl implements Cell {
}
public String toString() {
return "Cell: circuit_id="+ circuitId +" command="+ command +" payload_len="+ cellBuffer.position();
return "Cell: CircuitID=" + circuitId +" Command=" + command +" PayloadLength=" + cellBuffer.position();
}
public static String errorToDescription(int errorCode) {
switch(errorCode) {
case ERROR_NONE:
return "No error reason given";
return "\n* Reason: No error reason given";
case ERROR_PROTOCOL:
return "Tor protocol violation";
return "\n* Reason: Tor protocol violation";
case ERROR_INTERNAL:
return "Internal error";
return "\n* Reason: Internal error";
case ERROR_REQUESTED:
return "Response to a TRUNCATE command sent from client";
return "\n* Reason: Response to a TRUNCATE command sent from client";
case ERROR_HIBERNATING:
return "Not currently operating; trying to save bandwidth.";
return "\n* Reason: Not currently operating; trying to save bandwidth.";
case ERROR_RESOURCELIMIT:
return "Out of memory, sockets, or circuit IDs.";
return "\n* Reason: Out of memory, sockets, or circuit IDs.";
case ERROR_CONNECTFAILED:
return "Unable to reach server.";
return "\n* Reason: Unable to reach server.";
case ERROR_OR_IDENTITY:
return "Connected to server, but its OR identity was not as expected.";
return "\n* Reason: Connected to server, but its OR identity was not as expected.";
case ERROR_OR_CONN_CLOSED:
return "The OR connection that was carrying this circuit died.";
return "\n* Reason: The OR connection that was carrying this circuit died.";
case ERROR_FINISHED:
return "The circuit has expired for being dirty or old.";
return "\n* Reason: The circuit has expired for being dirty or old.";
case ERROR_TIMEOUT:
return "Circuit construction took too long.";
return "\n* Reason: Circuit construction took too long.";
case ERROR_DESTROYED:
return "The circuit was destroyed without client TRUNCATE";
return "\n* Reason: The circuit was destroyed without client TRUNCATE";
case ERROR_NOSUCHSERVICE:
return "Request for unknown hidden service";
return "\n* Reason: Request for unknown hidden service";
default:
return "Error code "+ errorCode;
return "\n* Reason: Error code " + errorCode;
}
}
}

View File

@ -5,6 +5,8 @@ import java.util.Set;
import com.subgraph.orchid.BridgeRouter;
import com.subgraph.orchid.Descriptor;
import com.subgraph.orchid.Directory;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.RouterDescriptor;
import com.subgraph.orchid.crypto.TorPublicKey;
import com.subgraph.orchid.data.HexDigest;
@ -18,7 +20,9 @@ public class BridgeRouterImpl implements BridgeRouter {
private HexDigest identity;
private Descriptor descriptor;
private Directory directory;
private volatile String cachedCountryCode;
private volatile String cachedCountryName;
BridgeRouterImpl(IPv4Address address, int port) {
this.address = address;
@ -29,6 +33,14 @@ public class BridgeRouterImpl implements BridgeRouter {
return address;
}
public String getPlatform() {
return null;
}
public int getUptime() {
return 0;
}
public HexDigest getIdentity() {
return identity;
}
@ -41,6 +53,10 @@ public class BridgeRouterImpl implements BridgeRouter {
this.descriptor = descriptor;
}
public void setDirectory(Directory directory) {
this.directory = directory;
}
@Override
public int hashCode() {
final int prime = 31;
@ -88,6 +104,15 @@ public class BridgeRouterImpl implements BridgeRouter {
return cc;
}
public String getCountryName() {
String cn = cachedCountryName;
if (cn == null) {
cn = CountryCodeService.getInstance().getCountryNameForAddress(getAddress());
cachedCountryName = cn;
}
return cn;
}
public int getOnionPort() {
return port;
}

View File

@ -11,12 +11,15 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import net.i2p.util.SystemVersion;
public class TorConfigImpl implements TorConfig {
private void resetDefaults() {
dataDirectory = toFile("~/.orchid");
circuitBuildTimeout = toMS(60, TimeUnit.SECONDS);
circuitStreamTimeout = 0;
// circuitStreamTimeout = 0; // a value of 0 will cause a request for an unavailable resource to never fail?
circuitStreamTimeout = toMS(90, TimeUnit.SECONDS);
circuitIdleTimeout = toMS(1, TimeUnit.HOURS);
newCircuitPeriod = toMS(30, TimeUnit.SECONDS);
maxCircuitDirtiness = toMS(10, TimeUnit.MINUTES);
@ -34,14 +37,19 @@ public class TorConfigImpl implements TorConfig {
fascistFirewall = false;
firewallPorts = toIntList("80,443");
safeSocks = false;
safeLogging = true;
// safeLogging = true; // ineffective as target info displays in logs
safeLogging = false;
warnUnsafeSocks = true;
clientRejectInternalAddress = true;
handshakeV3Enabled = true;
handshakeV2Enabled = true;
hsAuth = new TorConfigHSAuth();
useNtorHandshake = AutoBoolValue.AUTO;
// full descriptors OOMs for < 192MB
if (SystemVersion.getMaxMemory() < 180*1024*1024L)
useMicrodescriptors = AutoBoolValue.AUTO;
else
useMicrodescriptors = AutoBoolValue.FALSE; // pull the full descriptors for extra info e.g. uptime stats
useBridges = false;
bridgeLines = new ArrayList<TorConfigBridgeLine>();
}
@ -77,7 +85,6 @@ public class TorConfigImpl implements TorConfig {
private boolean useBridges;
private List<TorConfigBridgeLine> bridgeLines;
private static long toMS(long time, TimeUnit unit) {
return TimeUnit.MILLISECONDS.convert(time, unit);
}

View File

@ -116,7 +116,7 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
//throw new IllegalStateException("ConnectionCache has been closed");
throw new ConnectionFailedException("ConnectionCache has been closed");
}
logger.fine("Get connection to "+ router.getAddress() + " "+ router.getOnionPort() + " " + router.getNickname());
logger.fine("Connecting to [" + router.getNickname() + " (" + router.getAddress() + ":" + router.getOnionPort() +")]");
while(true) {
Future<ConnectionImpl> f = getFutureFor(router, isDirectoryConnection);
try {
@ -168,23 +168,29 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
if((flags & DASHBOARD_CONNECTIONS) == 0) {
return;
}
if (!getActiveConnections().isEmpty()) // not working.. conncache section should be displayed only when active circuits
writer.print("<tr><td>\n");
else
writer.print("<tr hidden><td>\n");
printDashboardBanner(writer, flags);
for(Connection c: getActiveConnections()) {
if(!c.isClosed()) {
renderer.renderComponent(writer, flags, c);
}
}
writer.println();
// close connection cache table
writer.print("</table>\n</td></tr>\n");
}
private void printDashboardBanner(PrintWriter writer, int flags) {
final boolean verbose = (flags & DASHBOARD_CONNECTIONS_VERBOSE) != 0;
if(verbose) {
writer.println("[Connection Cache (verbose)]");
} else {
writer.println("[Connection Cache]");
}
writer.println();
writer.print("<hr>\n<table id=\"conncache\" width=\"100%\">\n" +
"<tr><th colspan=\"5\" align=\"left\">Connection Cache</th></tr>\n<tr class=\"subtitle\">" +
"<th align=\"left\" width=\"25%\">Guard Node <span title=\"Circuits available for immediate use\">(Circuits)</span></th>" +
"<th align=\"left\" width=\"25%\">Idle</th>" +
"<th align=\"left\" width=\"25%\" title=\"Observed node bandwidth\">Bandwidth</th>" +
"<th align=\"left\" width=\"25%\">Uptime</th>" +
"</tr>\n");
}
List<Connection> getActiveConnections() {

View File

@ -190,7 +190,7 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
try {
output.write(cell.getCellBytes());
} catch (IOException e) {
logger.fine("IOException writing cell to connection "+ e.getMessage());
logger.fine("IOException writing cell to connection: " + e.getMessage());
closeSocket();
throw new ConnectionIOException(e.getClass().getName() + " : "+ e.getMessage());
}
@ -298,7 +298,7 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
try {
circuit = circuitMap.get(cell.getCircuitId());
if(circuit == null) {
logger.warning("Could not deliver relay cell for circuit id = "+ cell.getCircuitId() +" on connection "+ this +". Circuit not found");
logger.warning("Could not deliver relay cell for CircuitID=" + cell.getCircuitId() + " on connection " + this + ". Circuit not found");
return;
}
} finally {
@ -356,7 +356,7 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
}
public String toString() {
return "!" + router.getNickname() + "!";
return "[" + router.getNickname() + "]";
}
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
@ -370,9 +370,57 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
if(circuitCount == 0 && (flags & DASHBOARD_CONNECTIONS_VERBOSE) == 0) {
return;
}
writer.print(" [Connection router="+ router.getNickname());
writer.print(" circuits="+ circuitCount);
writer.print(" idle="+ (getIdleMilliseconds()/1000) + "s");
writer.println("]");
if (circuitCount > 0) {
String idHash = router.getIdentityHash().toString().toUpperCase();
writer.print("<tr><td><span class=\"nodecontainer\"><span class=\"hidden\">[</span><span class=\"node\" onclick=\"copyText();\">" +
"<span class=\"flag\" data-country=\"");
if (router.getCountryName() != null)
writer.print(router.getCountryName() + " (" + router.getAddress() + ")");
else
writer.print(router.getAddress());
writer.print("\"><img height=\"11\" width=\"16\" src=\"/flags.jsp?c=" +
router.getCountryCode().toLowerCase().replace("--", "a0") + "\"></span><span class=\"nickname\"");
if (router.getPlatform() != null)
writer.print(" data-ipv4=\"" + router.getPlatform().replace("Tor ", "").replace(" on ", " / ") + "\"");
writer.print(">");
writer.print("<a class=\"script\" href=\"https://metrics.torproject.org/rs.html#search/" + idHash + "\" target=\"_blank\">" +
router.getNickname() + "</a>" +
// <noscript> alternative lookup
"<noscript>" +
"<a href=\"https://torstatus.blutmagie.de/router_detail.php?FP=" + idHash + "\" target=\"_blank\">" +
router.getNickname() + "</a>" +
"</noscript>" +
"</span><span class=\"hidden\"> (</span><b>" + router.getAddress() + "</b>" +
"<span class=\"hidden\">)</span></span><span class=\"hidden\">]</span></span> <span class=\"circuitcount\">" + circuitCount + "</span></td>");
writer.print("<td><span class=\"nowrap\">" + (getIdleMilliseconds() / 1000) + "s</span></td>");
writer.print("<td>");
long bw = router.getObservedBandwidth();
writer.print("<span class=\"nowrap\">");
if (bw > 0 && bw < 1048576)
writer.print((bw / 1024) + " KB/s");
else if (bw >= 1048576)
writer.print(((bw / 1024) / 1024) + " MB/s");
else
writer.print("unknown");
writer.print("</span></td>");
writer.print("<td><span class=\"nowrap\">");
int uptime = router.getUptime();
if (uptime > 172800)
writer.print((((uptime / 60) / 60) / 24) + " days");
else if (uptime > 7200)
writer.print(((uptime / 60) / 60) + " hours");
else if (uptime > 120)
writer.print(uptime / 60 + " mins");
else if (uptime > 0)
writer.print(uptime + " secs");
else
writer.print("unknown");
writer.print("</span></td></tr>\n");
}
}
}

View File

@ -25,6 +25,7 @@ public class RouterImpl implements Router {
private Descriptor descriptor;
private volatile String cachedCountryCode;
private volatile String cachedCountryName;
protected RouterImpl(Directory directory, RouterStatus status) {
this.directory = directory;
@ -38,6 +39,7 @@ public class RouterImpl implements Router {
throw new TorException("Identity hash does not match status update");
this.status = status;
this.cachedCountryCode = null;
this.cachedCountryName = null;
this.descriptor = null;
refreshDescriptor();
}
@ -147,6 +149,15 @@ public class RouterImpl implements Router {
}
}
public String getPlatform() {
final RouterDescriptor rd = downcastDescriptor();
if (rd != null) {
return rd.getPlatform();
} else {
return null;
}
}
public String getNickname() {
return status.getNickname();
}
@ -173,6 +184,15 @@ public class RouterImpl implements Router {
}
}
public int getUptime() {
final RouterDescriptor rd = downcastDescriptor();
if (rd != null) {
return rd.getUptime();
} else {
return 0;
}
}
public boolean hasBandwidth() {
return status.hasBandwidth();
}
@ -237,7 +257,7 @@ public class RouterImpl implements Router {
}
public String toString() {
return "Router["+ getNickname() +" ("+getAddress() +":"+ getOnionPort() +")]";
return "[" + getNickname() + " (" + getAddress() +":" + getOnionPort() + ")]";
}
public String getCountryCode() {
@ -249,6 +269,15 @@ public class RouterImpl implements Router {
return cc;
}
public String getCountryName() {
String cn = cachedCountryName;
if (cn == null) {
cn = CountryCodeService.getInstance().getCountryNameForAddress(getAddress());
cachedCountryName = cn;
}
return cn;
}
private RouterDescriptor downcastDescriptor() {
refreshDescriptor();
if(descriptor instanceof RouterDescriptor) {

View File

@ -14,6 +14,7 @@ import com.subgraph.orchid.directory.parsing.DocumentParsingHandler;
/*
* This class contains the hardcoded 'bootstrap' directory authority
* server information.
* https://github.com/torproject/tor/blob/release-0.4.1/src/app/config/auth_dirs.inc
*/
public class TrustedAuthorities {
@ -27,7 +28,8 @@ public class TrustedAuthorities {
"authority Faravahar orport=443 v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 154.35.175.225:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC",
"authority gabelmoo orport=443 v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 131.188.40.189:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281",
"authority bastet orport=443 v3ident=27102BC123E7AF1D4741AE047E160C91ADC76B21 204.13.164.118:80 24E2 F139 121D 4394 C54B 5BCC 368B 3B41 1857 C413",
"authority Bifroest orport=443 bridge 37.218.247.217:80 1D8F 3A91 C37C 5D1C 4C19 B1AD 1D0C FBE8 BF72 D8E1",
// bridges don't work with orchid? and non-bridges listed after bridges don't work either
// "authority Bifroest orport=443 bridge 37.218.247.217:80 1D8F 3A91 C37C 5D1C 4C19 B1AD 1D0C FBE8 BF72 D8E1",
};
private final List<DirectoryServer> directoryServers = new ArrayList<DirectoryServer>();

View File

@ -86,8 +86,8 @@ public class KeyCertificateImpl implements KeyCertificate {
}
public String toString() {
return "(Certificate: address="+ directoryAddress +":"+ directoryPort
+" fingerprint="+ fingerprint +" published="+ keyPublished +" expires="+ keyExpires +")"+
return "[Certificate: address=" + directoryAddress + ":" + directoryPort
+ " fingerprint=" + fingerprint + " published=" + keyPublished + " expires=" + keyExpires + "]" +
"\nident="+ identityKey +" sign="+ signingKey;
}
}

View File

@ -42,8 +42,8 @@ public class RouterStatusImpl implements RouterStatus {
void setRejectedPorts(String portList) { this.exitPorts = ExitPorts.createRejectExitPorts(portList); }
public String toString() {
return "Router: ("+ nickname +" "+ identity +" "+ digest +" "+ address +" "+ routerPort +" " + directoryPort
+" "+ version +" "+ exitPorts +")";
return "Router: [" + nickname + " " + identity + " " + digest + " " + address + " " + routerPort + " " + directoryPort
+ " " + version + " " + exitPorts + "]";
}
public String getNickname() {
return nickname;

View File

@ -24,7 +24,9 @@ public enum RouterDescriptorKeyword {
WRITE_HISTORY("write-history"),
EVENTDNS("eventdns", 1),
CACHES_EXTRA_INFO("caches-extra-info", 0),
EXTRA_INFO_DIGEST("extra-info-digest", 1),
// could now be 1 or 2
// https://github.com/torproject/torspec/commit/0f03581e748d4867a009d3d9473d61a400a3f5c1
EXTRA_INFO_DIGEST("extra-info-digest"),
HIDDEN_SERVICE_DIR("hidden-service-dir"),
PROTOCOLS("protocols"),
ALLOW_SINGLE_HOP_EXITS("allow-single-hop-exits", 0),

View File

@ -11,6 +11,8 @@ import java.util.logging.Logger;
import com.subgraph.orchid.data.IPv4Address;
import net.i2p.I2PAppContext;
public class CountryCodeService {
private final static Logger logger = Logger.getLogger(CountryCodeService.class.getName());
private final static String DATABASE_FILENAME = "GeoIP.dat";
@ -48,6 +50,71 @@ public class CountryCodeService {
"WS", "YE", "YT", "RS", "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1",
"AX", "GG", "IM", "JE", "BL", "MF", "BQ", "SS", "O1" };
private static final String[] COUNTRY_NAMES = { "N/A", "Asia/Pacific Region",
"Europe", "Andorra", "United Arab Emirates", "Afghanistan",
"Antigua and Barbuda", "Anguilla", "Albania", "Armenia", "Curacao",
"Angola", "Antarctica", "Argentina", "American Samoa", "Austria",
"Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina",
"Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria",
"Bahrain", "Burundi", "Benin", "Bermuda", "Brunei Darussalam",
"Bolivia", "Brazil", "Bahamas", "Bhutan", "Bouvet Island",
"Botswana", "Belarus", "Belize", "Canada",
"Cocos (Keeling) Islands", "Congo, The Democratic Republic of the",
"Central African Republic", "Congo", "Switzerland",
"Cote D'Ivoire", "Cook Islands", "Chile", "Cameroon", "China",
"Colombia", "Costa Rica", "Cuba", "Cape Verde", "Christmas Island",
"Cyprus", "Czech Republic", "Germany", "Djibouti", "Denmark",
"Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia",
"Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia",
"Finland", "Fiji", "Falkland Islands (Malvinas)",
"Micronesia, Federated States of", "Faroe Islands", "France",
"Sint Maarten (Dutch part)", "Gabon", "United Kingdom", "Grenada",
"Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland",
"Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece",
"South Georgia and the South Sandwich Islands", "Guatemala",
"Guam", "Guinea-Bissau", "Guyana", "Hong Kong",
"Heard Island and McDonald Islands", "Honduras", "Croatia",
"Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India",
"British Indian Ocean Territory", "Iraq",
"Iran, Islamic Republic of", "Iceland", "Italy", "Jamaica",
"Jordan", "Japan", "Kenya", "Kyrgyzstan", "Cambodia", "Kiribati",
"Comoros", "Saint Kitts and Nevis",
"Korea, Democratic People's Republic of", "Korea, Republic of",
"Kuwait", "Cayman Islands", "Kazakhstan",
"Lao People's Democratic Republic", "Lebanon", "Saint Lucia",
"Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania",
"Luxembourg", "Latvia", "Libya", "Morocco", "Monaco",
"Moldova, Republic of", "Madagascar", "Marshall Islands",
"Macedonia", "Mali", "Myanmar", "Mongolia", "Macau",
"Northern Mariana Islands", "Martinique", "Mauritania",
"Montserrat", "Malta", "Mauritius", "Maldives", "Malawi", "Mexico",
"Malaysia", "Mozambique", "Namibia", "New Caledonia", "Niger",
"Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway",
"Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru",
"French Polynesia", "Papua New Guinea", "Philippines", "Pakistan",
"Poland", "Saint Pierre and Miquelon", "Pitcairn Islands",
"Puerto Rico", "Palestinian Territory", "Portugal", "Palau",
"Paraguay", "Qatar", "Reunion", "Romania", "Russian Federation",
"Rwanda", "Saudi Arabia", "Solomon Islands", "Seychelles", "Sudan",
"Sweden", "Singapore", "Saint Helena", "Slovenia",
"Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino",
"Senegal", "Somalia", "Suriname", "Sao Tome and Principe",
"El Salvador", "Syrian Arab Republic", "Swaziland",
"Turks and Caicos Islands", "Chad", "French Southern Territories",
"Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan",
"Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago",
"Tuvalu", "Taiwan", "Tanzania, United Republic of", "Ukraine",
"Uganda", "United States Minor Outlying Islands", "United States",
"Uruguay", "Uzbekistan", "Holy See (Vatican City State)",
"Saint Vincent and the Grenadines", "Venezuela",
"Virgin Islands, British", "Virgin Islands, U.S.", "Vietnam",
"Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte",
"Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",
"Anonymous Proxy", "Satellite Provider", "Other", "Aland Islands",
"Guernsey", "Isle of Man", "Jersey", "Saint Barthelemy",
"Saint Martin", "Bonaire, Saint Eustatius and Saba", "South Sudan",
"Other" };
private final byte[] database;
public CountryCodeService() {
@ -56,9 +123,13 @@ public class CountryCodeService {
private static byte[] loadDatabase() {
final InputStream input = openDatabaseStream();
final File dataDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins/orchid/geoip");
final File dbFile = new File(dataDir, DATABASE_FILENAME);
if(input == null) {
logger.warning("Failed to open '"+ DATABASE_FILENAME + "' database file for country code lookups");
logger.warning("Failed to open '" + DATABASE_FILENAME + "' database file in " + dataDir + " for country code lookups");
return null;
} else {
logger.info("Loaded '" + dataDir + "/" + DATABASE_FILENAME + "' for country code lookups");
}
try {
return loadEntireStream(input);
@ -82,7 +153,7 @@ public class CountryCodeService {
}
private static InputStream tryFilesystemOpen() {
final File dataDir = new File(System.getProperty("user.dir"), "data");
final File dataDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins/orchid/geoip");
final File dbFile = new File(dataDir, DATABASE_FILENAME);
if(!dbFile.canRead()) {
return null;
@ -95,7 +166,8 @@ public class CountryCodeService {
}
private static InputStream tryResourceOpen() {
return CountryCodeService.class.getResourceAsStream("/data/"+ DATABASE_FILENAME);
final File dataDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins/orchid/geoip");
return CountryCodeService.class.getResourceAsStream(dataDir + "/" + DATABASE_FILENAME);
}
private static byte[] loadEntireStream(InputStream input) throws IOException {
@ -119,6 +191,10 @@ public class CountryCodeService {
return COUNTRY_CODES[seekCountry(address)];
}
public String getCountryNameForAddress(IPv4Address address) {
return COUNTRY_NAMES[seekCountry(address)];
}
private int seekCountry(IPv4Address address) {
if(database == null) {
return 0;
@ -140,7 +216,7 @@ public class CountryCodeService {
if(xx >= COUNTRY_BEGIN) {
final int idx = xx - COUNTRY_BEGIN;
if(idx < 0 || idx > COUNTRY_CODES.length) {
logger.warning("Invalid index calculated looking up country code record for ("+ address +") idx = "+ idx);
logger.warning("Invalid index calculated looking up country code record for: [" + address + "] idx = " + idx);
return 0;
} else {
return idx;
@ -150,7 +226,7 @@ public class CountryCodeService {
}
}
logger.warning("No record found looking up country code record for ("+ address + ")");
logger.warning("No country code record found for: " + address);
return 0;
}

View File

@ -26,6 +26,7 @@ import com.subgraph.orchid.config.TorConfigBridgeLine;
import com.subgraph.orchid.config.TorConfigInterval;
import com.subgraph.orchid.config.TorConfigParser;
import com.subgraph.orchid.dashboard.Dashboard;
import com.subgraph.orchid.geoip.CountryCodeService;
import net.i2p.I2PAppContext;
import net.i2p.app.*;
@ -94,7 +95,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
if (_mgr != null)
_mgr.register(this);
if (_log.shouldLog(Log.INFO))
_log.info("Orchid ready");
_log.info("Orchid plugin ready");
}
/**
@ -106,7 +107,8 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
throw new IllegalStateException();
changeState(STARTING);
if (_log.shouldLog(Log.INFO))
_log.info("Starting Orchid");
_log.info("Starting Orchid plugin...");
// TODO config dir
try {
_logger = new OrchidLogHandler(_context);
@ -114,6 +116,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
_client.getConfig().setDataDirectory(_configDir);
loadConfig(_client.getConfig());
_client.addInitializationListener(this);
CountryCodeService.getInstance();
_client.start();
} catch (RuntimeException t) {
// TorException extends RuntimeException,
@ -191,7 +194,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
return;
changeState(STOPPING);
if (_log.shouldLog(Log.INFO))
_log.info("Stopping Orchid");
_log.info("Stopping Orchid plugin...");
if (_mgr != null)
_mgr.unregister(this);
if (_client != null) {
@ -204,13 +207,13 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
}
changeState(STOPPED);
if (_log.shouldLog(Log.INFO))
_log.info("Orchid stopped");
_log.info("Orchid plugin stopped");
}
public Socket connect(String host, int port) throws IOException {
if (host.equals("127.0.0.1") || host.equals("localhost") ||
host.toLowerCase(Locale.US).endsWith(".i2p"))
throw new IOException("unsupported host " + host);
throw new IOException("unsupported host: " + host);
ClientAppState state = _state;
if (state != RUNNING)
throw new IOException("Cannot connect in state " + state);
@ -224,7 +227,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
IOException ioe = new IOException("connect error");
ioe.initCause(e);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error Connecting to " + host + ':' + port, ioe);
_log.debug("Error connecting to " + host + ':' + port + "\n* Reason:" + ioe.getMessage());
throw ioe;
}
}
@ -246,11 +249,11 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
DataHelper.loadProps(p, _configFile);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("error loading config file", ioe);
_log.warn("Error loading Orchid config file \n* Reason: " + ioe.getMessage());
return;
}
if (_log.shouldLog(Log.INFO))
_log.info("Loading " + p.size() + " configuations");
_log.info("Loading " + p.size() + " Orchid configuration options");
TorConfigParser tcp = new TorConfigParser();
for (Map.Entry e : p.entrySet()) {
String k = (String) e.getKey();
@ -318,7 +321,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
tc.setWarnUnsafeSocks((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Unknown config entry " + k + " = " + v);
_log.warn("Invalid Orchid config entry: " + k + " = " + v);
}
}
}
@ -331,7 +334,8 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
PrintWriter pw = new PrintWriter(sw);
(new Dashboard()).renderComponent(pw, 0xff, _client.getCircuitManager());
pw.close();
out.write(DataHelper.escapeHTML(sw.toString()));
out.write(sw.toString());
}
}

View File

@ -35,7 +35,25 @@ public class OrchidLogHandler extends Handler {
public void publish(LogRecord record) {
Log log = _mgr.getLog(record.getLoggerName());
int level = toI2PLevel(record.getLevel());
log.log(level, record.getMessage(), record.getThrown());
// fix logs so they don't spew html tags; various cleanups & detritus removal
log.log(level, "[Orchid] " + record.getMessage().replaceAll("<noscript>((?:.*?\r?\n?)*)</noscript>", "")
.replaceAll("<.+?>", " ")
.replaceAll("Building&hellip;", "")
.replaceAll("&hellip;", "...")
.replaceAll("Target=", "\\\n* Target: ")
.replaceAll("Circuit=", "CircuitID=")
.replaceAll("Exit ", "")
.replaceAll("Open ", "")
.replaceAll("( )+", " ")
.replaceAll("\\(.+?\\)", "")
.replaceAll("\\[ ", "\\[")
.replaceAll(" \\]", "\\]")
.replaceAll(" +\\]", "\\]")
.replaceAll(" =", "=")
.replaceAll("= ", "=")
.replaceAll("\\] -> \\[", " -> ")
.replaceAll("\\( ", "\\(")
.replaceAll(" \\)", "\\)"), record.getThrown());
}
private static int toI2PLevel(Level level) {

View File

@ -9,6 +9,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.subgraph.orchid.Tor;
import com.subgraph.orchid.TorConfig;
import net.i2p.app.ClientAppManager;
@ -16,6 +17,8 @@ import net.i2p.orchid.OrchidController;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Translate;
import net.i2p.I2PAppContext;
/**
* From base in i2psnark
*/
@ -30,10 +33,10 @@ public class OrchidServlet extends BasicServlet {
private static final String DEFAULT_NAME = "orchid";
public static final String PROP_CONFIG_FILE = "orchid.configFile";
private static final String DOCTYPE = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
private static final String FOOTER = "</div></body></html>";
private static final String DOCTYPE = "<!DOCTYPE HTML>\n";
private static final String FOOTER = "</div>\n<span id=\"endOfPage\" data-iframe-height></span>\n</body>\n</html>";
private static final String BUNDLE = "net.i2p.orchid.messages";
private static final String RESOURCES = "/orchid/resources/";
public OrchidServlet() {
super();
@ -118,7 +121,7 @@ public class OrchidServlet extends BasicServlet {
*/
private void doGetAndPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
// this is the part after /i2psnark
// this is the part after /orchid
String path = req.getServletPath();
resp.setHeader("X-Frame-Options", "SAMEORIGIN");
@ -127,67 +130,126 @@ public class OrchidServlet extends BasicServlet {
resp.setContentType("text/html; charset=UTF-8");
PrintWriter out = resp.getWriter();
out.write(DOCTYPE + "<html>\n" +
"<head>\n" +
"<title>");
out.write(_t("Orchid Controller"));
out.write(DOCTYPE + "<html>\n<head>\n<title>");
out.write(_t("Orchid Tor Client"));
out.write("</title>\n");
out.write("<meta http-equiv=\"Content-Security-Policy\" content=\"script-src \'self\' \'unsafe-inline\';\">\n");
out.write("<link rel=\"icon\" href=\"" + RESOURCES + "images/favicon.png\">\n");
out.write("<link href=\"" + RESOURCES + "orchid.css\" rel=\"stylesheet\" type=\"text/css\">\n");
out.write("<noscript><style>.script, #expand, #collapse {display: none !important;} " +
"#configuration {display: table !important;} *::selection {color: #fff; background: #77f;}" +
".node:active {cursor: text;}</style></noscript>\n");
out.write("</head>\n");
out.write("<body><div><h3>Plugin Status</h3>");
out.write("<body id=\"orchid\" onload=\"hideConfig();\">\n<div id=\"container\">\n<table id=\"main\" width=\"100%\">\n" +
"<thead><tr><th id=\"title\" align=\"left\">Orchid Tor Client</th></tr></thead>\n");
out.write("<tbody>\n<tr><td>\n<hr>\n<table id=\"status\" width=\"100%\">\n<tr class=\"subtitle\">" +
"<th width=\"33%\">Status</th><th width=\"33%\">Registered with I2P</th><th width=\"33%\">Plugin Version</th></tr>\n");
out.write("<tr><td align=\"center\">");
OrchidController c = _manager;
String status = c.getState().toString();
if (c != null) {
out.write("Status is: " + c.getState());
ClientAppManager cam = _context.clientAppManager();
if (cam != null)
out.write("<br>Registered? " + (cam.getRegisteredApp("outproxy") != null));
if (status.equals("RUNNING"))
out.write("<span id=\"running\">Running</span>");
else if (status.equals("STARTING"))
out.write("<span id=\"starting\">Starting...</span>");
else
out.write("<br>Not registered, no client manager");
out.write("<h3>Circuit Status</h3><pre>");
// not really in HTML for now
out.write("" + c.getState());
} else {
out.write("Not initialized");
}
out.write("</td><td align=\"center\">");
ClientAppManager cam = _context.clientAppManager();
if (c != null && cam != null) {
if (cam.getRegisteredApp("outproxy") != null) {
// out.write("<span id=\"registered\">" + (cam.getRegisteredApp("outproxy") != null) + "</span>");
out.write("<span id=\"registered\">Yes</span>");
} else {
out.write("<span id=\"starting\">In progress...</span>");
}
} else {
out.write("<span id=\"notregistered\" title=\"Not registered, no client manager\">Not registered, no client manager</span>");
}
out.write("</td>" +
"<td align=\"center\">" +
Tor.getFullVersion() + "</td></tr>\n</table>\n");
if (c != null) {
out.write("<hr>\n<!-- Circuit Status -->\n<tr><th id=\"circuitstatus\" align=\"left\">Circuit Status&nbsp;" +
"<span id=\"refresh\" style=\"float: right;\"><a href=\".\">Refresh</a></span></th></tr>\n");
out.write("<tr><td>\n<hr>\n");
// yes, really in HTML now!
c.renderStatusHTML(out);
out.write("</pre>");
out.write("</table>\n</td></tr>\n<!-- end Circuit Status -->\n");
TorConfig tc = c.getConfig();
if (tc != null)
out.write("<tr id=\"configsection\"><td>\n<hr>\n<div id=\"configtitle\"><b>Configuration Parameters</b>&nbsp;\n" +
"<a class=\"script\" id=\"expand\" href=\"#\" onclick=\"clean();expand();\"><img alt=\"Expand\" src=\"/orchid/resources/images/expand.png\" title=\"Expand\"></a>\n" +
"<a class=\"script\" id=\"collapse\" href=\"#\" onclick=\"clean();collapse();\"><img alt=\"Collapse\" src=\"/orchid/resources/images/collapse.png\" title=\"Collapse\"></a></div>\n");
out.write(getHTMLConfig(tc));
} else {
out.write("Status is: UNINITIALIZED");
}
out.write("<script src=\"" + RESOURCES + "ajaxRefresh.js\" type=\"application/javascript\"></script>\n");
out.write("<script src=\"" + RESOURCES + "toggleConfig.js\" type=\"application/javascript\"></script>\n");
out.write(FOOTER);
}
private static String getHTMLConfig(TorConfig tc) {
File configPath = I2PAppContext.getGlobalContext().getConfigDir();
String slash = System.getProperty("file.separator");
StringBuilder buf = new StringBuilder(1024);
buf.append("<h3>Configuration</h3>");
buf.append("<table cellspacing=\"8\">");
buf.append("<tr><th>Config</th><th>Value</th></tr>");
buf.append("<tr><td>Bridges</td><td>").append(tc.getBridges()).append("</tr>");
buf.append("<tr><td>Circuit Build Timeout</td><td>").append(tc.getCircuitBuildTimeout()).append("</tr>");
buf.append("<tr><td>Circuit Idle Timeout</td><td>").append(tc.getCircuitIdleTimeout()).append("</tr>");
buf.append("<tr><td>Circuit Stream Timeout</td><td>").append(tc.getCircuitStreamTimeout()).append("</tr>");
buf.append("<tr><td>Client Reject Internal Address</td><td>").append(tc.getClientRejectInternalAddress()).append("</tr>");
buf.append("<tr><td>Enforce Distinct Subnets</td><td>").append(tc.getEnforceDistinctSubnets()).append("</tr>");
buf.append("<tr><td>Entry Guards</td><td>").append(tc.getNumEntryGuards()).append("</tr>");
buf.append("<tr><td>Entry Nodes</td><td>").append(tc.getEntryNodes()).append("</tr>");
buf.append("<tr><td>Exclude Exit Nodes</td><td>").append(tc.getExcludeExitNodes()).append("</tr>");
buf.append("<tr><td>Exclude Nodes</td><td>").append(tc.getExcludeNodes()).append("</tr>");
buf.append("<tr><td>Exit Nodes</td><td>").append(tc.getExitNodes()).append("</tr>");
buf.append("<tr><td>Fascist Firewall</td><td>").append(tc.getFascistFirewall()).append("</tr>");
buf.append("<tr><td>Firewall Ports</td><td>").append(tc.getFirewallPorts()).append("</tr>");
buf.append("<tr><td>Handshake V2 Enabled</td><td>").append(tc.getHandshakeV2Enabled()).append("</tr>");
buf.append("<tr><td>Handshake V3 Enabled</td><td>").append(tc.getHandshakeV3Enabled()).append("</tr>");
buf.append("<tr><td>Long Lived Ports</td><td>").append(tc.getLongLivedPorts()).append("</tr>");
buf.append("<tr><td>Max Circuit Dirtiness</td><td>").append(tc.getMaxCircuitDirtiness()).append("</tr>");
buf.append("<tr><td>Max Client Circuits Pending</td><td>").append(tc.getMaxClientCircuitsPending()).append("</tr>");
buf.append("<tr><td>New Circuit Period</td><td>").append(tc.getNewCircuitPeriod()).append("</tr>");
buf.append("<tr><td>Safe Logging</td><td>").append(tc.getSafeLogging()).append("</tr>");
buf.append("<tr><td>Safe Socks</td><td>").append(tc.getSafeSocks()).append("</tr>");
buf.append("<tr><td>Strict Nodes</td><td>").append(tc.getStrictNodes()).append("</tr>");
buf.append("<tr><td>Use Bridges</td><td>").append(tc.getUseBridges()).append("</tr>");
buf.append("<tr><td>Use Microdescriptors</td><td>").append(tc.getUseMicrodescriptors()).append("</tr>");
buf.append("<tr><td>Use NTor Handshake</td><td>").append(tc.getUseNTorHandshake()).append("</tr>");
buf.append("<tr><td>Warn Unsafe Socks</td><td>").append(tc.getWarnUnsafeSocks()).append("</tr>");
buf.append("</table>");
buf.append("<hr>\n<table id=\"configuration\" width=\"100%\">\n");
buf.append("<tr><td class=\"notice\" colspan=\"3\">");
buf.append("The configuration is stored at <code>" + configPath + slash + "plugins" + slash + "orchid" + slash + "orchid.config</code>. " +
"Any changes will require a restart of the plugin to take effect. " +
"For more information on the configuration options, see <a href=\"https://www.torproject.org/docs/tor-manual.html.en\" target=\"_blank\">Tor's Online Manual</a>.");
buf.append("</td></tr>\n");
buf.append("<tr><th align=\"left\">Setting Name</th><th align=\"left\">Value <i>(hint)</i></th><th align=\"left\" width=\"50%\">Notes</th></tr>\n");
buf.append("<tr><td>Bridges</td><td><code>");
if (!tc.getBridges().isEmpty())
buf.append(tc.getBridges());
buf.append("</code></td><td>See <a href=\"https://bridges.torproject.org\" target=\"_blank\">bridges.torproject.org</a></td></tr>\n");
buf.append("<tr><td>Circuit Build Timeout</td><td><code>").append(tc.getCircuitBuildTimeout()).append("</code> <span class=\"nowrap\">(")
.append(tc.getCircuitBuildTimeout() / 1000).append(" seconds)").append("</span></td><td>Time limit for new circuit build</td></tr>\n");
buf.append("<tr><td>Circuit Idle Timeout</td><td><code>").append(tc.getCircuitIdleTimeout()).append("</code> <span class=\"nowrap\">(")
.append((tc.getCircuitIdleTimeout() / 1000) / 60).append(" minutes)</span>").append("</td><td>Expire circuit if unused for period</td></tr>\n");
buf.append("<tr><td>Circuit Stream Timeout</td><td><code>").append(tc.getCircuitStreamTimeout()).append("</code> <span class=\"nowrap\">(")
.append(tc.getCircuitStreamTimeout() / 1000).append(" seconds)</span>").append("</td><td>Timeout for trying new circuit if stream fails</td></tr>\n");
buf.append("<tr><td>Client Reject Internal Address</td><td><code>").append(tc.getClientRejectInternalAddress()).append("</code></td><td>Reject connection attempts to internal addresses</td></tr>\n");
buf.append("<tr><td>Enforce Distinct Subnets</td><td><code>").append(tc.getEnforceDistinctSubnets()).append("</code></td><td>Don't use nodes from the same /16 in a single circuit</td></tr>\n");
buf.append("<tr><td>Entry Nodes</td><td><code>");
if (!tc.getEntryNodes().isEmpty())
buf.append(tc.getEntryNodes());
buf.append("</code></td><td>List of desired entry nodes</td></tr>\n");
buf.append("<tr><td>Exclude Exit Nodes</td><td><code>");
if (!tc.getExcludeExitNodes().isEmpty())
buf.append(tc.getExcludeExitNodes());
buf.append("</code></td><td>Don't use specified nodes as Exits</td></tr>\n");
buf.append("<tr><td>Exclude Nodes</td><td><code>");
if (!tc.getExcludeNodes().isEmpty())
buf.append(tc.getExcludeNodes());
buf.append("</code></td><td>Don't use specified nodes in any circuit</td></tr>\n");
buf.append("<tr><td>Exit Nodes</td><td><code>");
if (!tc.getExitNodes().isEmpty())
buf.append(tc.getExitNodes());
buf.append("</code></td><td>Limit Exit nodes to those specified</td></tr>\n");
buf.append("<tr><td>Fascist Firewall</td><td><code>").append(tc.getFascistFirewall()).append("</code></td><td>Only connect to nodes using &lt;Firewall Ports&gt;</td></tr>\n");
buf.append("<tr><td>Firewall Ports</td><td><code>").append(tc.getFirewallPorts()).append("</code></td><td>Specify outgoing ports if behind strict firewall</td></tr>\n");
buf.append("<tr><td>Handshake V2 Enabled</td><td><code>").append(tc.getHandshakeV2Enabled()).append("</code></td><td>Use version 2 of the Tor handshake</td></tr>\n");
buf.append("<tr><td>Handshake V3 Enabled</td><td><code>").append(tc.getHandshakeV3Enabled()).append("</code></td><td>Use version 3 of the Tor handshake</td></tr>\n");
buf.append("<tr><td>Long Lived Ports</td><td><code>").append(tc.getLongLivedPorts()).append("</code></td><td>Map these ports to high-uptime nodes</td></tr>\n");
buf.append("<tr><td>Max Circuit Dirtiness</td><td><code>").append(tc.getMaxCircuitDirtiness()).append("</code> <span class=\"nowrap\">(")
.append((tc.getMaxCircuitDirtiness() / 1000) / 60).append(" minutes)</span>").append("</td><td>Max time to use circuit before cycling</td></tr>\n");
buf.append("<tr><td>Max Client Circuits Pending</td><td><code>").append(tc.getMaxClientCircuitsPending()).append("</code></td><td>Max number of circuit builds in progess</td></tr>\n");
buf.append("<tr><td>New Circuit Period</td><td><code>").append(tc.getNewCircuitPeriod()).append("</code> <span class=\"nowrap\">(")
.append(tc.getNewCircuitPeriod() / 1000).append(" seconds)</span>").append("</td><td>Period of new circuit build consideration</td></tr>\n");
buf.append("<tr><td>Number of Entry Guards</td><td><code>").append(tc.getNumEntryGuards()).append("</code></td><td>Number of long-term entry guards to use if &lt;Use Entry Guards&gt; is set to <i>true</i></td></tr>\n");
buf.append("<tr><td>Safe Logging</td><td><code>").append(tc.getSafeLogging()).append("</code></td><td>Scrub sensitive strings (eg. addresses) from logs</td></tr>\n");
buf.append("<tr><td>Safe Socks</td><td><code>").append(tc.getSafeSocks()).append("</code></td><td>Reject requests that only provide ip address</td></tr>\n");
buf.append("<tr><td>Strict Nodes</td><td><code>").append(tc.getStrictNodes()).append("</code></td><td>Use excluded nodes if necessary for network task</td></tr>\n");
buf.append("<tr><td>Use Bridges</td><td><code>").append(tc.getUseBridges()).append("</code></td><td>Use &lt;bridges&gt; to connect to network</td></tr>\n");
buf.append("<tr><td>Use Entry Guards</td><td><code>").append(tc.getUseEntryGuards()).append("</code></td><td>Select a few long-term entry nodes and stick with them</td></tr>\n");
buf.append("<tr><td>Use Microdescriptors</td><td><code>").append(tc.getUseMicrodescriptors()).append("</code></td><td>Use bandwidth-saving info to build circuits</td></tr>\n");
buf.append("<tr><td>Use NTor Handshake</td><td><code>").append(tc.getUseNTorHandshake()).append("</code></td><td>Use the ntor circuit-creation handshake</td></tr>\n");
buf.append("<tr><td>Warn Unsafe Socks</td><td><code>").append(tc.getWarnUnsafeSocks()).append("</code></td><td>Warn when requests only provide ip address</td></tr>\n");
buf.append("</table>\n</td></tr>\n</table>\n");
return buf.toString();
}

View File

@ -11,7 +11,7 @@
<servlet-mapping>
<servlet-name>net.i2p.orchid.web.OrchidServlet</servlet-name>
<url-pattern>/</url-pattern>
<url-pattern></url-pattern>
</servlet-mapping>
<servlet-mapping>