Compare commits
35 Commits
orchid-0.4
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bd8b31845a | ||
c7e0df48bb | |||
29bdda5d0a | |||
a7a9b2642a | |||
cd53feb4a7 | |||
5350fcae7e | |||
c4038916c4 | |||
d0ef965cd9 | |||
861f7e94ee | |||
b701694222 | |||
d8fee276f0 | |||
9c88c24c84 | |||
6d3523ba32 | |||
78234aeba7 | |||
25a1f4e710 | |||
9a3b7c12ca | |||
1821a76194 | |||
71b7b4b2a5 | |||
da1198a45c | |||
e7befc9c45 | |||
9419bfd337 | |||
47a1372fbd | |||
682a94fff6 | |||
78c674ea28 | |||
d9b2aa77f0 | |||
f2ada83acb | |||
8741e066d7 | |||
5cdb55a6c1 | |||
cae24e352a | |||
3a1e800a00 | |||
ac18316253 | |||
c0c41a90a3 | |||
8ec0c3e6d2 | |||
76b79e6952 | |||
e0d70176a1 |
66
.github/workflows/sync.yaml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
# GitHub Actions workflow file to sync an external repository to this GitHub mirror.
|
||||
# This file was automatically generated by go-github-sync.
|
||||
#
|
||||
# The workflow does the following:
|
||||
# - Runs on a scheduled basis (and can also be triggered manually)
|
||||
# - Clones the GitHub mirror repository
|
||||
# - Fetches changes from the primary external repository
|
||||
# - Applies those changes to the mirror repository
|
||||
# - Pushes the updated content back to the GitHub mirror
|
||||
#
|
||||
# Authentication is handled by the GITHUB_TOKEN secret provided by GitHub Actions.
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate Github Actions Environment
|
||||
run: if [ "$GITHUB_ACTIONS" != "true" ]; then echo 'This script must be run in a GitHub Actions environment.'; exit 1; fi
|
||||
- name: Checkout GitHub Mirror
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Configure Git
|
||||
run: |-
|
||||
git config user.name 'GitHub Actions'
|
||||
git config user.email 'actions@github.com'
|
||||
- env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Sync Primary Repository
|
||||
run: |-
|
||||
# Add the primary repository as a remote
|
||||
git remote add primary https://i2pgit.org/I2P_Developers/i2p.plugins.orchid.git
|
||||
|
||||
# Fetch the latest changes from the primary repository
|
||||
git fetch primary
|
||||
|
||||
# Check if the primary branch exists in the primary repository
|
||||
if git ls-remote --heads primary master | grep -q master; then
|
||||
echo "Primary branch master found in primary repository"
|
||||
else
|
||||
echo "Error: Primary branch master not found in primary repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we're already on the mirror branch
|
||||
if git rev-parse --verify --quiet master; then
|
||||
git checkout master
|
||||
else
|
||||
# Create the mirror branch if it doesn't exist
|
||||
git checkout -b master
|
||||
fi
|
||||
|
||||
|
||||
# Force-apply all changes from primary, overriding any conflicts
|
||||
echo "Performing force sync from primary/master to master"
|
||||
git reset --hard primary/master
|
||||
|
||||
|
||||
# Push changes back to the mirror repository
|
||||
git push origin master
|
||||
name: Sync Primary Repository to GitHub Mirror
|
||||
"on":
|
||||
push: {}
|
||||
schedule:
|
||||
- cron: 0 * * * *
|
||||
workflow_dispatch: {}
|
69
CHANGES.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
* 2019-08-21 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
|
||||
- Update dizum dirauth IP
|
||||
|
||||
* 2019-06-24 1.2.2-0.5
|
||||
- Remove unused XMLRPC Transport and related libs
|
||||
- Reduce max descriptor age
|
||||
- Enforce max certificates file age
|
||||
|
||||
* 2019-01-16 1.2.2-0.4
|
||||
- Remove restrictive SSL cipher list which caused nothing to work (ticket #2079)
|
||||
|
||||
* 2018-04-15 1.2.2-0.3
|
||||
- Reduce max consensus age again (ticket #1220)
|
||||
- Don't load a cached-microdesc from disk that is too old (ticket #1937)
|
||||
- Update dirauths (ticket #2079)
|
||||
- Fix spurious errors at shutdown (ticket #2079)
|
||||
- Fix for new certificate types in tor-spec 4.2 (ticket #2079)
|
||||
|
||||
* 2016-08-14 1.2.2-0.2
|
||||
- Add Stream.isClosed()
|
||||
- Implement more methods in TorStreamSocket
|
||||
- Escape HTML on status page
|
||||
- Fix HTML error on status page
|
||||
|
||||
* 2016-08-05 1.2.2-0.1
|
||||
- Don't load a consensus from disk that is too old
|
||||
(ticket #1220, orchid issue #24, bitcoinj issue #1064)
|
||||
- Fix reopening HS circuit after close (ticket #1251)
|
||||
- Delay registration until initialization complete
|
||||
- HTTPConnection.getHost() fix
|
||||
|
||||
* 2016-08-03 1.2.1-0.1 (unreleased)
|
||||
- Merge of bitcoinj changes, including deadlock fixes (ticket #1207),
|
||||
NPE fix (ticket #1221), and configuration rewrite
|
||||
- ConfigNodeFilter.createIdentityFilter() fix
|
||||
|
||||
* 2016-07-30 1.0.0-0.5
|
||||
- Update hardcoded dirauths
|
||||
- Add su3 plugin build support
|
||||
|
||||
* 2014-03-01 1.0.0-0.4
|
||||
- Catch policy exception to correctly set failed state (ticket #1201)
|
||||
- Hopefully fix deadlock (ticket #1207)
|
||||
|
||||
* 2014-01-10 1.0.0-0.3
|
||||
- Better logging of startup errors
|
||||
- Add config file support
|
||||
- Add circuit status to servlet
|
||||
|
||||
* 2014-01-08 1.0.0-0.2
|
||||
- Change from jsp to java servlet
|
||||
- Start controller from servlet, not clients.config, to avoid class loader issues
|
||||
- Fix webapps.config location
|
||||
- Fix console link
|
||||
- Fix classpath issues
|
||||
- Add status info using TorConfig
|
||||
|
||||
* 2014-01-04 1.0.0-0.1
|
||||
- Initial checkin
|
@@ -1,3 +1,3 @@
|
||||
I2P license: TBD
|
||||
I2P license: 3-clause BSD
|
||||
|
||||
Bundled software: see plugins/licenses
|
||||
|
44
build.xml
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project basedir="." default="all" name="orchid">
|
||||
<property file="override.properties"/>
|
||||
|
||||
<target name="all" depends="clean,plugin" />
|
||||
|
||||
<property name="xmlrpc.version" value="3.1.3" />
|
||||
|
||||
<target name="war" >
|
||||
<ant dir="src" target="build" />
|
||||
</target>
|
||||
@@ -12,11 +11,19 @@
|
||||
<target name="plugin" depends="war">
|
||||
<!-- get version number -->
|
||||
<buildnumber file="scripts/build.number" />
|
||||
<property name="release.number" value="1.0.0-0.4" />
|
||||
<property name="release.number" value="1.2.2-0.6" />
|
||||
|
||||
<!-- 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" />
|
||||
@@ -34,10 +41,18 @@
|
||||
<arg value="plugin/console/webapps/orchid.war.pack" />
|
||||
<arg value="src/build/orchid.war.jar" />
|
||||
</exec>
|
||||
<exec executable="scripts/makeplugin.sh" failonerror="true" >
|
||||
<input message="Enter su3 signing key password:" addproperty="release.password.su3" />
|
||||
<fail message="You must enter a password." >
|
||||
<condition>
|
||||
<equals arg1="${release.password.su3}" arg2=""/>
|
||||
</condition>
|
||||
</fail>
|
||||
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
|
||||
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
|
||||
<arg value="plugin" />
|
||||
</exec>
|
||||
<move file="orchid.xpi2p" tofile="orchid-update.xpi2p" overwrite="true" />
|
||||
<move file="orchid.su3" tofile="orchid-update.su3" overwrite="true" />
|
||||
|
||||
<!-- make the install xpi2p -->
|
||||
<copy file="scripts/orchid.config" todir="plugin/" overwrite="true" />
|
||||
@@ -45,17 +60,7 @@
|
||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||
<arg value="version=${release.number}-b${build.number}" />
|
||||
</exec>
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-g" />
|
||||
<arg value="plugin/lib/xmlrpc-client.jar.pack" />
|
||||
<arg value="lib/xmlrpc-client-${xmlrpc.version}.jar" />
|
||||
</exec>
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-g" />
|
||||
<arg value="plugin/lib/xmlrpc-common.jar.pack" />
|
||||
<arg value="lib/xmlrpc-common-${xmlrpc.version}.jar" />
|
||||
</exec>
|
||||
<exec executable="scripts/makeplugin.sh" failonerror="true" >
|
||||
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
|
||||
<arg value="plugin" />
|
||||
</exec>
|
||||
</target>
|
||||
@@ -64,17 +69,26 @@
|
||||
|
||||
<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" />
|
||||
<delete file="plugin/lib/orchid.jar.pack" />
|
||||
<!-- following two removed in 0.5 -->
|
||||
<delete file="plugin/lib/xmlrpc-common.jar.pack" />
|
||||
<delete file="plugin/lib/xmlrpc-client.jar.pack" />
|
||||
<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" />
|
||||
<delete file="orchid-update.su3" />
|
||||
</target>
|
||||
|
||||
</project>
|
||||
|
BIN
geoip/GeoIP.dat
Normal file
8
geoip/README.txt
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
BIN
resources/images/circuits.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/images/collapse.png
Normal file
After Width: | Height: | Size: 275 B |
BIN
resources/images/configure.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/cross.png
Normal file
After Width: | Height: | Size: 559 B |
BIN
resources/images/directory.png
Normal file
After Width: | Height: | Size: 826 B |
BIN
resources/images/exit.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
resources/images/expand.png
Normal file
After Width: | Height: | Size: 281 B |
BIN
resources/images/favicon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/images/globe.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
resources/images/hiddenservice.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/infohelp.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/images/internal.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/images/node.png
Normal file
After Width: | Height: | Size: 576 B |
BIN
resources/images/orchid.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
resources/images/refresh.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/images/rx.png
Normal file
After Width: | Height: | Size: 662 B |
BIN
resources/images/starting.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/images/tag.png
Normal file
After Width: | Height: | Size: 813 B |
BIN
resources/images/tag_stream.png
Normal file
After Width: | Height: | Size: 801 B |
BIN
resources/images/target.png
Normal file
After Width: | Height: | Size: 892 B |
BIN
resources/images/tick.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
resources/images/tile2.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/images/tx.png
Normal file
After Width: | Height: | Size: 765 B |
966
resources/orchid.css
Normal 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
@@ -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");
|
||||
}
|
@@ -5,58 +5,95 @@
|
||||
# usage: makeplugin.sh plugindir
|
||||
#
|
||||
# zzz 2010-02
|
||||
# zzz 2014-08 added support for su3 files
|
||||
#
|
||||
|
||||
if [ -z "$I2P" -a -d "$PWD/../i2p/pkg-temp" ]; then
|
||||
export I2P=$PWD/../i2p/pkg-temp
|
||||
fi
|
||||
|
||||
if [ ! -d "$I2P" ]; then
|
||||
echo "Can't locate your I2P installation. Please add a environment variable named I2P with the path to the folder as value"
|
||||
echo "On OSX this solved with running: export I2P=/Applications/i2p if default install directory is used."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CPATH=$I2P/lib/i2p.jar:/usr/share/java/gnu-getopt.jar
|
||||
PUBKEYDIR=$HOME/.i2p-plugin-keys
|
||||
PUBKEYFILE=$PUBKEYDIR/plugin-public-signing.key
|
||||
PRIVKEYFILE=$PUBKEYDIR/plugin-private-signing.key
|
||||
B64KEYFILE=$PUBKEYDIR/plugin-public-signing.txt
|
||||
export I2P=../i2p/pkg-temp
|
||||
PUBKEYSTORE=$PUBKEYDIR/plugin-su3-public-signing.crt
|
||||
PRIVKEYSTORE=$PUBKEYDIR/plugin-su3-keystore.ks
|
||||
KEYTYPE=RSA_SHA512_4096
|
||||
|
||||
# put your files in here
|
||||
PLUGINDIR=${1:-plugin}
|
||||
|
||||
PC=plugin.config
|
||||
PCT=${PC}.tmp
|
||||
|
||||
if [ ! -f $PRIVKEYFILE ]
|
||||
then
|
||||
mkdir -p $PUBKEYDIR
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
|
||||
rm -rf logs/
|
||||
chmod 444 $PUBKEYFILE $B64KEYFILE
|
||||
chmod 400 $PRIVKEYFILE
|
||||
echo "Created new keys: $PUBKEYFILE $PRIVKEYFILE"
|
||||
fi
|
||||
|
||||
rm -f plugin.zip
|
||||
if [ ! -d $PLUGINDIR ]
|
||||
if [ ! -d "$PLUGINDIR" ]
|
||||
then
|
||||
echo "You must have a $PLUGINDIR directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OPWD=$PWD
|
||||
cd $PLUGINDIR
|
||||
|
||||
if [ ! -f $PC ]
|
||||
if [ ! -f "$PLUGINDIR/$PC" ]
|
||||
then
|
||||
echo "You must have a $PC file"
|
||||
echo "You must have a $PLUGINDIR/$PC file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -q '^signer=' $PC
|
||||
SIGNER=`grep '^signer=' "$PLUGINDIR/$PC"`
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a signer in $PC"
|
||||
echo 'For example signer=joe@mail.i2p'
|
||||
echo "You must have a signer name in $PC"
|
||||
echo 'For example name=foo'
|
||||
exit 1
|
||||
fi
|
||||
SIGNER=`echo $SIGNER | cut -f 2 -d '='`
|
||||
|
||||
if [ ! -f "$PRIVKEYFILE" ]
|
||||
then
|
||||
echo "Creating new XPI2P DSA keys"
|
||||
mkdir -p "$PUBKEYDIR" || exit 1
|
||||
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate keygen "$PUBKEYFILE" "$PRIVKEYFILE" || exit 1
|
||||
java -cp "$CPATH" net.i2p.data.Base64 encode "$PUBKEYFILE" "$B64KEYFILE" || exit 1
|
||||
rm -rf logs/
|
||||
chmod 444 "$PUBKEYFILE" "$B64KEYFILE"
|
||||
chmod 400 "$PRIVKEYFILE"
|
||||
echo "Created new XPI2P keys: $PUBKEYFILE $PRIVKEYFILE"
|
||||
fi
|
||||
|
||||
if [ ! -f "$PRIVKEYSTORE" ]
|
||||
then
|
||||
echo "Creating new SU3 $KEYTYPE keys for $SIGNER"
|
||||
java -cp "$CPATH" net.i2p.crypto.SU3File keygen -t $KEYTYPE "$PUBKEYSTORE" "$PRIVKEYSTORE" $SIGNER || exit 1
|
||||
echo '*** Save your password in a safe place!!! ***'
|
||||
rm -rf logs/
|
||||
# copy to the router dir so verify will work
|
||||
CDIR=$I2P/certificates/plugin
|
||||
mkdir -p "$CDIR" || exit 1
|
||||
CFILE=$CDIR/`echo $SIGNER | sed s/@/_at_/`.crt
|
||||
cp "$PUBKEYSTORE" "$CFILE"
|
||||
chmod 444 "$PUBKEYSTORE"
|
||||
chmod 400 "$PRIVKEYSTORE"
|
||||
chmod 644 "$CFILE"
|
||||
echo "Created new SU3 keys: $PUBKEYSTORE $PRIVKEYSTORE"
|
||||
echo "Copied public key to $CFILE for testing"
|
||||
fi
|
||||
|
||||
rm -f plugin.zip
|
||||
|
||||
OPWD=$PWD
|
||||
cd "$PLUGINDIR"
|
||||
|
||||
grep -q '^name=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a plugin name in $PC"
|
||||
echo 'For example name=foo'
|
||||
echo 'For example name=foo'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -64,41 +101,49 @@ grep -q '^version=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a version in $PC"
|
||||
echo 'For example version=0.1.2'
|
||||
echo 'For example version=0.1.2'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# update the date
|
||||
grep -v '^date=' $PC > $PCT
|
||||
DATE=`date '+%s000'`
|
||||
echo "date=$DATE" >> $PCT
|
||||
mv $PCT $PC
|
||||
echo "date=$DATE" >> $PCT || exit 1
|
||||
mv $PCT $PC || exit 1
|
||||
|
||||
# add our Base64 key
|
||||
grep -v '^key=' $PC > $PCT
|
||||
B64KEY=`cat $B64KEYFILE`
|
||||
B64KEY=`cat "$B64KEYFILE"`
|
||||
echo "key=$B64KEY" >> $PCT || exit 1
|
||||
mv $PCT $PC
|
||||
mv $PCT $PC || exit 1
|
||||
|
||||
# zip it
|
||||
zip -r $OPWD/plugin.zip * -x \*.jar || exit 1
|
||||
zip -r "$OPWD/plugin.zip" * || exit 1
|
||||
|
||||
# get the version and use it for the sud header
|
||||
VERSION=`grep '^version=' $PC | cut -f 2 -d '='`
|
||||
# get the name and use it for the file name
|
||||
NAME=`grep '^name=' $PC | cut -f 2 -d '='`
|
||||
XPI2P=${NAME}.xpi2p
|
||||
cd $OPWD
|
||||
SU3=${NAME}.su3
|
||||
cd "$OPWD"
|
||||
|
||||
# sign it
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || exit 1
|
||||
echo 'Signing. ...'
|
||||
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate sign plugin.zip "$XPI2P" "$PRIVKEYFILE" "$VERSION" || exit 1
|
||||
java -cp "$CPATH" net.i2p.crypto.SU3File sign -c PLUGIN -t $KEYTYPE plugin.zip "$SU3" "$PRIVKEYSTORE" "$VERSION" "$SIGNER" || exit 1
|
||||
rm -f plugin.zip
|
||||
|
||||
# verify
|
||||
echo 'Verifying. ...'
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion $XPI2P || exit 1
|
||||
java -cp $I2P/lib/i2p.jar -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig $XPI2P || exit 1
|
||||
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate showversion "$XPI2P" || exit 1
|
||||
java -cp "$CPATH" -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig "$XPI2P" || exit 1
|
||||
java -cp "$CPATH" net.i2p.crypto.SU3File showversion "$SU3" || exit 1
|
||||
java -cp "$CPATH" net.i2p.crypto.SU3File verifysig -k "$PUBKEYSTORE" "$SU3" || exit 1
|
||||
rm -rf logs/
|
||||
|
||||
echo -n 'Plugin created: '
|
||||
wc -c $XPI2P
|
||||
echo 'Plugin files created: '
|
||||
wc -c "$XPI2P"
|
||||
wc -c "$SU3"
|
||||
|
||||
exit 0
|
||||
|
@@ -5,7 +5,9 @@ consoleLinkURL=/orchid/
|
||||
description=Tor Outproxy
|
||||
author=bruce@subgraph.com (packaged by zzz)
|
||||
updateURL=http://stats.i2p/i2p/plugins/orchid-update.xpi2p
|
||||
updateURL.su3=http://stats.i2p/i2p/plugins/orchid-update.su3
|
||||
websiteURL=http://zzz.i2p/forums/16
|
||||
license=BSD
|
||||
min-java-version=1.7
|
||||
min-jetty-version=7
|
||||
min-i2p-version=0.9.11
|
||||
|
@@ -1,18 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2psnark">
|
||||
<project basedir="." default="all" name="orchid">
|
||||
<property name="i2pbase" value="../../i2p.i2p"/>
|
||||
<property name="i2plib" value="${i2pbase}/build"/>
|
||||
<property name="jettylib" value="${i2pbase}/apps/jetty/jettylib"/>
|
||||
<path id="cp">
|
||||
<pathelement path="${java.class.path}" />
|
||||
<pathelement location="${i2plib}/i2p.jar" />
|
||||
<pathelement location="${ant.home}/lib/ant.jar"/>
|
||||
<pathelement location="${jettylib}/jasper-runtime.jar" />
|
||||
<pathelement location="${jettylib}/javax.servlet.jar" />
|
||||
<pathelement location="${jettylib}/commons-logging.jar" />
|
||||
<pathelement location="${jettylib}/commons-el.jar" />
|
||||
<pathelement location="../lib/xmlrpc-client-${xmlrpc.version}.jar" />
|
||||
<pathelement location="../lib/xmlrpc-common-${xmlrpc.version}.jar" />
|
||||
<pathelement location="${jettylib}/jetty-util.jar" />
|
||||
</path>
|
||||
|
||||
<target name="all" depends="clean, build" />
|
||||
@@ -21,16 +17,17 @@
|
||||
</target>
|
||||
|
||||
<property name="javac.compilerargs" value="" />
|
||||
<property name="javac.version" value="1.7" />
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./java"
|
||||
debug="true" deprecation="on" source="1.5" target="1.5"
|
||||
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
|
||||
destdir="./build/obj"
|
||||
includeAntRuntime="false"
|
||||
classpath="${i2plib}/i2p.jar:../lib/xmlrpc-client-${xmlrpc.version}.jar:../lib/xmlrpc-common-${xmlrpc.version}.jar:${jettylib}/javax.servlet.jar" >
|
||||
classpath="${i2plib}/i2p.jar:${jettylib}/javax.servlet.jar" >
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
@@ -81,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>
|
||||
|
||||
|
@@ -23,6 +23,9 @@ public interface Circuit {
|
||||
boolean isClean();
|
||||
|
||||
boolean isMarkedForClose();
|
||||
|
||||
/** @since 1.2.2 */
|
||||
boolean isClosed();
|
||||
|
||||
int getSecondsDirty();
|
||||
|
||||
|
@@ -18,4 +18,6 @@ public interface ConnectionCache {
|
||||
Connection getConnectionTo(Router router, boolean isDirectoryConnection) throws InterruptedException, ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException;
|
||||
|
||||
void close();
|
||||
|
||||
boolean isClosed();
|
||||
}
|
||||
|
@@ -16,6 +16,10 @@ public interface DirectoryServer extends Router {
|
||||
boolean isBridgeAuthority();
|
||||
boolean isExtraInfoCache();
|
||||
|
||||
/**
|
||||
* https://github.com/geo-gs/Orchid/commit/22beeae1b881707491addaba6a7654e9de9f9db1
|
||||
*/
|
||||
KeyCertificate getCertificateByAuthority(HexDigest fingerprint);
|
||||
KeyCertificate getCertificateByFingerprint(HexDigest fingerprint);
|
||||
List<KeyCertificate> getCertificates();
|
||||
void addCertificate(KeyCertificate certificate);
|
||||
|
@@ -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();
|
||||
|
@@ -26,6 +26,9 @@ public interface Stream {
|
||||
*/
|
||||
void close();
|
||||
|
||||
/** @since 1.2.2-0.2 */
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} for sending data on this stream.
|
||||
*
|
||||
|
107
src/java/com/subgraph/orchid/Threading.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
//import com.google.common.util.concurrent.CycleDetectingLockFactory;
|
||||
//import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class Threading {
|
||||
static {
|
||||
// Default policy goes here. If you want to change this, use one of the static methods before
|
||||
// instantiating any orchid objects. The policy change will take effect only on new objects
|
||||
// from that point onwards.
|
||||
throwOnLockCycles();
|
||||
}
|
||||
|
||||
//private static CycleDetectingLockFactory.Policy policy;
|
||||
//public static CycleDetectingLockFactory factory;
|
||||
|
||||
public static ReentrantLock lock(String name) {
|
||||
//return factory.newReentrantLock(name);
|
||||
return new ReentrantLock();
|
||||
}
|
||||
|
||||
public static void warnOnLockCycles() {
|
||||
//setPolicy(CycleDetectingLockFactory.Policies.WARN);
|
||||
}
|
||||
|
||||
public static void throwOnLockCycles() {
|
||||
//setPolicy(CycleDetectingLockFactory.Policies.THROW);
|
||||
}
|
||||
|
||||
public static void ignoreLockCycles() {
|
||||
//setPolicy(CycleDetectingLockFactory.Policies.DISABLED);
|
||||
}
|
||||
|
||||
/****
|
||||
public static void setPolicy(CycleDetectingLockFactory.Policy policy) {
|
||||
Threading.policy = policy;
|
||||
factory = CycleDetectingLockFactory.newInstance(policy);
|
||||
}
|
||||
|
||||
public static CycleDetectingLockFactory.Policy getPolicy() {
|
||||
return policy;
|
||||
}
|
||||
****/
|
||||
|
||||
public static ExecutorService newPool(final String name) {
|
||||
//ThreadFactory factory = new ThreadFactoryBuilder()
|
||||
// .setDaemon(true)
|
||||
// .setNameFormat(name + "-%d").build();
|
||||
ThreadFactory factory = new CustomThreadFactory(name);
|
||||
return Executors.newCachedThreadPool(factory);
|
||||
}
|
||||
|
||||
public static ScheduledExecutorService newSingleThreadScheduledPool(final String name) {
|
||||
//ThreadFactory factory = new ThreadFactoryBuilder()
|
||||
// .setDaemon(true)
|
||||
// .setNameFormat(name + "-%d").build();
|
||||
ThreadFactory factory = new CustomThreadFactory(name);
|
||||
return Executors.newSingleThreadScheduledExecutor(factory);
|
||||
}
|
||||
|
||||
public static ScheduledExecutorService newScheduledPool(final String name) {
|
||||
//ThreadFactory factory = new ThreadFactoryBuilder()
|
||||
// .setDaemon(true)
|
||||
// .setNameFormat(name + "-%d").build();
|
||||
ThreadFactory factory = new CustomThreadFactory(name);
|
||||
return Executors.newScheduledThreadPool(1, factory);
|
||||
}
|
||||
|
||||
/*
|
||||
* Modified from Guava ThreadFactorBuilder
|
||||
*
|
||||
* Copyright (C) 2010 The Guava Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
private static class CustomThreadFactory implements ThreadFactory {
|
||||
private final String fmt;
|
||||
private final AtomicLong count = new AtomicLong();
|
||||
|
||||
public CustomThreadFactory(String name) {
|
||||
fmt = name + "-%d";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable runnable) {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
|
||||
thread.setName(String.format(fmt, count.getAndIncrement()));
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@ import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.circuits.CircuitManagerImpl;
|
||||
import com.subgraph.orchid.circuits.TorInitializationTracker;
|
||||
import com.subgraph.orchid.config.TorConfigProxy;
|
||||
import com.subgraph.orchid.config.TorConfigImpl;
|
||||
import com.subgraph.orchid.connections.ConnectionCacheImpl;
|
||||
import com.subgraph.orchid.directory.DirectoryImpl;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
||||
@@ -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();
|
||||
|
||||
@@ -93,7 +93,7 @@ public class Tor {
|
||||
* @see TorConfig
|
||||
*/
|
||||
static public TorConfig createConfig() {
|
||||
final TorConfig config = (TorConfig) Proxy.newProxyInstance(TorConfigProxy.class.getClassLoader(), new Class[] { TorConfig.class }, new TorConfigProxy());
|
||||
final TorConfig config = new TorConfigImpl();
|
||||
if(isAndroidRuntime()) {
|
||||
logger.warning("Android Runtime detected, disabling V2 Link protocol");
|
||||
config.setHandshakeV2Enabled(false);
|
||||
|
@@ -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;
|
||||
}
|
||||
@@ -117,10 +117,6 @@ public class TorClient {
|
||||
return circuitManager;
|
||||
}
|
||||
|
||||
public DirectoryDownloader getDirectoryDownloader() {
|
||||
return directoryDownloader;
|
||||
}
|
||||
|
||||
public void waitUntilReady() throws InterruptedException {
|
||||
readyLatch.await();
|
||||
}
|
||||
@@ -212,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");
|
||||
|
@@ -16,136 +16,97 @@ import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
public interface TorConfig {
|
||||
|
||||
@ConfigVar(type=ConfigVarType.PATH, defaultValue="~/.orchid")
|
||||
File getDataDirectory();
|
||||
void setDataDirectory(File directory);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="60 seconds")
|
||||
long getCircuitBuildTimeout();
|
||||
void setCircuitBuildTimeout(long time, TimeUnit unit);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="0")
|
||||
long getCircuitStreamTimeout();
|
||||
void setCircuitStreamTimeout(long time, TimeUnit unit);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="1 hour")
|
||||
long getCircuitIdleTimeout();
|
||||
void setCircuitIdleTimeout(long time, TimeUnit unit);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="30 seconds")
|
||||
long getNewCircuitPeriod();
|
||||
void setNewCircuitPeriod(long time, TimeUnit unit);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="10 minutes")
|
||||
long getMaxCircuitDirtiness();
|
||||
void setMaxCircuitDirtiness(long time, TimeUnit unit);
|
||||
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTEGER, defaultValue="32")
|
||||
int getMaxClientCircuitsPending();
|
||||
void setMaxClientCircuitsPending(int value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getEnforceDistinctSubnets();
|
||||
void setEnforceDistinctSubnets(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="2 minutes")
|
||||
long getSocksTimeout();
|
||||
void setSocksTimeout(long value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.INTEGER, defaultValue="3")
|
||||
int getNumEntryGuards();
|
||||
void setNumEntryGuards(int value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getUseEntryGuards();
|
||||
void setUseEntryGuards(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.PORTLIST, defaultValue="21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300")
|
||||
List<Integer> getLongLivedPorts();
|
||||
void setLongLivedPorts(List<Integer> ports);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
||||
List<String> getExcludeNodes();
|
||||
void setExcludeNodes(List<String> nodes);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
||||
List<String> getExcludeExitNodes();
|
||||
|
||||
void setExcludeExitNodes(List<String> nodes);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
||||
List<String> getExitNodes();
|
||||
void setExitNodes(List<String> nodes);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.STRINGLIST)
|
||||
List<String> getEntryNodes();
|
||||
void setEntryNodes(List<String> nodes);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
||||
boolean getStrictNodes();
|
||||
void setStrictNodes(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
||||
boolean getFascistFirewall();
|
||||
void setFascistFirewall(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.PORTLIST, defaultValue="80,443")
|
||||
List<Integer> getFirewallPorts();
|
||||
void setFirewallPorts(List<Integer> ports);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
||||
boolean getSafeSocks();
|
||||
void setSafeSocks(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getSafeLogging();
|
||||
void setSafeLogging(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getWarnUnsafeSocks();
|
||||
void setWarnUnsafeSocks(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getClientRejectInternalAddress();
|
||||
void setClientRejectInternalAddress(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getHandshakeV3Enabled();
|
||||
void setHandshakeV3Enabled(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
|
||||
boolean getHandshakeV2Enabled();
|
||||
void setHandshakeV2Enabled(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.HS_AUTH)
|
||||
HSDescriptorCookie getHidServAuth(String key);
|
||||
void addHidServAuth(String key, String value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.AUTOBOOL, defaultValue="auto")
|
||||
AutoBoolValue getUseNTorHandshake();
|
||||
void setUseNTorHandshake(AutoBoolValue value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.AUTOBOOL, defaultValue="auto")
|
||||
AutoBoolValue getUseMicrodescriptors();
|
||||
void setUseMicrodescriptors(AutoBoolValue value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
|
||||
boolean getUseBridges();
|
||||
void setUseBridges(boolean value);
|
||||
|
||||
@ConfigVar(type=ConfigVarType.BRIDGE_LINE)
|
||||
List<TorConfigBridgeLine> getBridges();
|
||||
void addBridge(IPv4Address address, int port);
|
||||
void addBridge(IPv4Address address, int port, HexDigest fingerprint);
|
||||
|
||||
enum ConfigVarType { INTEGER, STRING, HS_AUTH, BOOLEAN, INTERVAL, PORTLIST, STRINGLIST, PATH, AUTOBOOL, BRIDGE_LINE };
|
||||
enum AutoBoolValue { TRUE, FALSE, AUTO }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@interface ConfigVar {
|
||||
ConfigVarType type();
|
||||
String defaultValue() default "";
|
||||
}
|
||||
}
|
@@ -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("]");
|
||||
|
@@ -5,7 +5,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -19,6 +18,7 @@ import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.ExitCircuit;
|
||||
import com.subgraph.orchid.InternalCircuit;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.CircuitManagerImpl.CircuitFilter;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
@@ -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;
|
||||
@@ -52,7 +54,7 @@ public class CircuitCreationTask implements Runnable {
|
||||
this.circuitManager = circuitManager;
|
||||
this.initializationTracker = initializationTracker;
|
||||
this.pathChooser = pathChooser;
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
this.executor = Threading.newPool("CircuitCreationTask worker");
|
||||
this.buildHandler = createCircuitBuildHandler();
|
||||
this.internalBuildHandler = createInternalCircuitBuildHandler();
|
||||
this.predictor = new CircuitPredictor();
|
||||
@@ -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;
|
||||
}
|
||||
@@ -142,6 +144,11 @@ public class CircuitCreationTask implements Runnable {
|
||||
}
|
||||
|
||||
private void buildCircuitIfNeeded() {
|
||||
if (connectionCache.isClosed()) {
|
||||
logger.warning("Not building circuits, because connection cache is closed");
|
||||
return;
|
||||
}
|
||||
|
||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
||||
final List<PredictedPortTarget> predictedPorts = predictor.getPredictedPortTargets();
|
||||
final List<ExitTarget> exitTargets = new ArrayList<ExitTarget>();
|
||||
@@ -223,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());
|
||||
}
|
||||
@@ -265,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) {
|
||||
@@ -283,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);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -18,6 +19,7 @@ import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.ConnectionIOException;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.cells.CellImpl;
|
||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
||||
@@ -36,11 +38,10 @@ public class CircuitIO implements DashboardRenderable {
|
||||
private final BlockingQueue<RelayCell> relayCellResponseQueue;
|
||||
private final BlockingQueue<Cell> controlCellResponseQueue;
|
||||
private final Map<Integer, StreamImpl> streamMap;
|
||||
private final Object relaySendLock = new Object();
|
||||
|
||||
/** LOCKING: streamMap */
|
||||
private final ReentrantLock streamLock = Threading.lock("stream");
|
||||
private final ReentrantLock relaySendLock = Threading.lock("relaySend");
|
||||
|
||||
private boolean isMarkedForClose;
|
||||
/** LOCKING: streamMap */
|
||||
private boolean isClosed;
|
||||
|
||||
CircuitIO(CircuitImpl circuit, Connection connection, int circuitId) {
|
||||
@@ -120,7 +121,7 @@ public class CircuitIO implements DashboardRenderable {
|
||||
}
|
||||
|
||||
private void processDestroyCell(int reason) {
|
||||
logger.fine("DESTROY cell received ("+ CellImpl.errorToDescription(reason) +") on "+ circuit);
|
||||
logger.fine("DESTROY cell received on " + circuit + CellImpl.errorToDescription(reason));
|
||||
destroyCircuit();
|
||||
}
|
||||
|
||||
@@ -171,7 +172,8 @@ public class CircuitIO implements DashboardRenderable {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
final StreamImpl stream = streamMap.get(cell.getStreamId());
|
||||
// It's not unusual for the stream to not be found. For example, if a RELAY_CONNECTED arrives after
|
||||
// the client has stopped waiting for it, the stream will never be tracked and eventually the edge node
|
||||
@@ -179,6 +181,8 @@ public class CircuitIO implements DashboardRenderable {
|
||||
if(stream != null) {
|
||||
stream.addInputCell(cell);
|
||||
}
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +191,8 @@ public class CircuitIO implements DashboardRenderable {
|
||||
}
|
||||
|
||||
void sendRelayCellTo(RelayCell cell, CircuitNode targetNode) {
|
||||
synchronized(relaySendLock) {
|
||||
relaySendLock.lock();
|
||||
try {
|
||||
logRelayCell("Sending: ", cell);
|
||||
cell.setLength();
|
||||
targetNode.updateForwardDigest(cell);
|
||||
@@ -200,6 +205,8 @@ public class CircuitIO implements DashboardRenderable {
|
||||
targetNode.waitForSendWindowAndDecrement();
|
||||
|
||||
sendCell(cell);
|
||||
} finally {
|
||||
relaySendLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,31 +243,35 @@ public class CircuitIO implements DashboardRenderable {
|
||||
|
||||
void markForClose() {
|
||||
boolean shouldClose;
|
||||
synchronized (streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
if(isMarkedForClose) {
|
||||
return;
|
||||
}
|
||||
isMarkedForClose = true;
|
||||
shouldClose = streamMap.isEmpty();
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
if (shouldClose)
|
||||
if(shouldClose)
|
||||
closeCircuit();
|
||||
}
|
||||
|
||||
boolean isMarkedForClose() {
|
||||
synchronized (streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
return isMarkedForClose;
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void closeCircuit() {
|
||||
logger.fine("Closing circuit "+ circuit);
|
||||
logger.fine("Closing CircuitID="+ circuit);
|
||||
sendDestroyCell(Cell.ERROR_NONE);
|
||||
connection.removeCircuit(circuit);
|
||||
circuit.setStateDestroyed();
|
||||
synchronized(streamMap) {
|
||||
isClosed = true;
|
||||
}
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
void sendDestroyCell(int reason) {
|
||||
@@ -278,7 +289,8 @@ public class CircuitIO implements DashboardRenderable {
|
||||
}
|
||||
|
||||
void destroyCircuit() {
|
||||
synchronized(streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
if(isClosed) {
|
||||
return;
|
||||
}
|
||||
@@ -289,31 +301,42 @@ public class CircuitIO implements DashboardRenderable {
|
||||
s.close();
|
||||
}
|
||||
isClosed = true;
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
StreamImpl createNewStream(boolean autoclose) {
|
||||
synchronized(streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
final int streamId = circuit.getStatus().nextStreamId();
|
||||
final StreamImpl stream = new StreamImpl(circuit, circuit.getFinalCircuitNode(), streamId, autoclose);
|
||||
streamMap.put(streamId, stream);
|
||||
return stream;
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void removeStream(StreamImpl stream) {
|
||||
boolean shouldClose;
|
||||
synchronized(streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
streamMap.remove(stream.getStreamId());
|
||||
shouldClose = streamMap.isEmpty() && isMarkedForClose;
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
if (shouldClose)
|
||||
if(shouldClose)
|
||||
closeCircuit();
|
||||
}
|
||||
|
||||
|
||||
List<Stream> getActiveStreams() {
|
||||
synchronized (streamMap) {
|
||||
streamLock.lock();
|
||||
try {
|
||||
return new ArrayList<Stream>(streamMap.values());
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -124,6 +124,11 @@ public abstract class CircuitImpl implements Circuit, DashboardRenderable {
|
||||
return !status.isDirty();
|
||||
}
|
||||
|
||||
/** @since 1.2.2 */
|
||||
public boolean isClosed() {
|
||||
return status.isClosed();
|
||||
}
|
||||
|
||||
public int getSecondsDirty() {
|
||||
return (int) (status.getMillisecondsDirty() / 1000);
|
||||
}
|
||||
@@ -229,7 +234,10 @@ public abstract class CircuitImpl implements Circuit, DashboardRenderable {
|
||||
}
|
||||
|
||||
public void destroyCircuit() {
|
||||
io.destroyCircuit();
|
||||
// We might not have bound this circuit yet
|
||||
if (io != null) {
|
||||
io.destroyCircuit();
|
||||
}
|
||||
circuitManager.removeActiveCircuit(this);
|
||||
}
|
||||
|
||||
@@ -253,19 +261,87 @@ 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();
|
||||
long bw = r.getObservedBandwidth();
|
||||
long bwEst = r.getEstimatedBandwidth();
|
||||
sb.append("<span class=\"nickname");
|
||||
if (r.getPlatform() != null || r.getUptime() > 0 || r.getObservedBandwidth() > 0 || r.getEstimatedBandwidth() > 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");
|
||||
// useMicrodescriptors=true
|
||||
else if (bwEst > 0 && bwEst < 2048)
|
||||
sb.append(bwEst + " KB/s (estimated)");
|
||||
else if (bwEst >= 2048)
|
||||
sb.append((bwEst / 1024) + " MB/s (estimated)");
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@@ -11,8 +11,10 @@ import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitBuildHandler;
|
||||
@@ -29,6 +31,7 @@ import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.guards.EntryGuards;
|
||||
@@ -57,11 +60,14 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
|
||||
private int pendingInternalCircuitCount = 0;
|
||||
private final TorRandom random;
|
||||
private final PendingExitStreams pendingExitStreams;
|
||||
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ScheduledExecutorService scheduledExecutor = Threading.newSingleThreadScheduledPool("CircuitManager worker");
|
||||
private final CircuitCreationTask circuitCreationTask;
|
||||
private final TorInitializationTracker initializationTracker;
|
||||
private final CircuitPathChooser pathChooser;
|
||||
private final HiddenServiceManager hiddenServiceManager;
|
||||
private final ReentrantLock lock = Threading.lock("circuitManager");
|
||||
|
||||
private boolean isBuilding = false;
|
||||
|
||||
public CircuitManagerImpl(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker initializationTracker) {
|
||||
this.config = config;
|
||||
@@ -84,14 +90,30 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
|
||||
}
|
||||
|
||||
public void startBuildingCircuits() {
|
||||
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
lock.lock();
|
||||
try {
|
||||
isBuilding = true;
|
||||
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopBuildingCircuits(boolean killCircuits) {
|
||||
scheduledExecutor.shutdownNow();
|
||||
if(killCircuits) {
|
||||
List<CircuitImpl> circuits = new ArrayList<CircuitImpl>(activeCircuits);
|
||||
for(CircuitImpl c: circuits) {
|
||||
public void stopBuildingCircuits(boolean killCircuits) {
|
||||
lock.lock();
|
||||
try {
|
||||
isBuilding = false;
|
||||
scheduledExecutor.shutdownNow();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
if (killCircuits) {
|
||||
ArrayList<CircuitImpl> circuits;
|
||||
synchronized (activeCircuits) {
|
||||
circuits = new ArrayList<CircuitImpl>(activeCircuits);
|
||||
}
|
||||
for (CircuitImpl c : circuits) {
|
||||
c.destroyCircuit();
|
||||
}
|
||||
}
|
||||
@@ -106,16 +128,31 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
|
||||
activeCircuits.add(circuit);
|
||||
activeCircuits.notifyAll();
|
||||
}
|
||||
|
||||
boolean doDestroy;
|
||||
lock.lock();
|
||||
try {
|
||||
doDestroy = !isBuilding;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
if (doDestroy) {
|
||||
// we were asked to stop since this circuit was started
|
||||
circuit.destroyCircuit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void removeActiveCircuit(CircuitImpl circuit) {
|
||||
synchronized (activeCircuits) {
|
||||
activeCircuits.remove(circuit);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized int getActiveCircuitCount() {
|
||||
return activeCircuits.size();
|
||||
int getActiveCircuitCount() {
|
||||
synchronized (activeCircuits) {
|
||||
return activeCircuits.size();
|
||||
}
|
||||
}
|
||||
|
||||
Set<Circuit> getPendingCircuits() {
|
||||
@@ -126,17 +163,28 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
|
||||
});
|
||||
}
|
||||
|
||||
synchronized int getPendingCircuitCount() {
|
||||
return getPendingCircuits().size();
|
||||
int getPendingCircuitCount() {
|
||||
lock.lock();
|
||||
try {
|
||||
return getPendingCircuits().size();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Set<Circuit> getCircuitsByFilter(CircuitFilter filter) {
|
||||
final Set<Circuit> result = new HashSet<Circuit>();
|
||||
final Set<CircuitImpl> circuits = new HashSet<CircuitImpl>();
|
||||
|
||||
synchronized (activeCircuits) {
|
||||
for(CircuitImpl c: activeCircuits) {
|
||||
if(filter == null || filter.filter(c)) {
|
||||
result.add(c);
|
||||
}
|
||||
// the filter might lock additional objects, causing a deadlock, so don't
|
||||
// call it inside the monitor
|
||||
circuits.addAll(activeCircuits);
|
||||
}
|
||||
|
||||
for(CircuitImpl c: circuits) {
|
||||
if(filter == null || filter.filter(c)) {
|
||||
result.add(c);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -182,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,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 {
|
||||
|
@@ -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>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ public class CircuitStatus {
|
||||
|
||||
enum CircuitState {
|
||||
UNCONNECTED("Unconnected"),
|
||||
BUILDING("Building"),
|
||||
BUILDING("<i>Building…</i>"),
|
||||
FAILED("Failed"),
|
||||
OPEN("Open"),
|
||||
DESTROYED("Destroyed");
|
||||
@@ -89,6 +89,11 @@ public class CircuitStatus {
|
||||
return state == CircuitState.UNCONNECTED;
|
||||
}
|
||||
|
||||
/** @since 1.2.2 */
|
||||
boolean isClosed() {
|
||||
return state == CircuitState.FAILED || state == CircuitState.DESTROYED;
|
||||
}
|
||||
|
||||
String getStateAsString() {
|
||||
if(state == CircuitState.OPEN) {
|
||||
return state.toString() + " ["+ getDirtyString() + "]";
|
||||
@@ -98,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() {
|
||||
|
@@ -37,6 +37,6 @@ public class DirectoryCircuitImpl extends CircuitImpl implements DirectoryCircui
|
||||
|
||||
@Override
|
||||
protected String getCircuitTypeLabel() {
|
||||
return "Directory";
|
||||
return "<span class=\"directory\">Directory</span>";
|
||||
}
|
||||
}
|
||||
|
@@ -82,6 +82,6 @@ public class ExitCircuitImpl extends CircuitImpl implements ExitCircuit {
|
||||
|
||||
@Override
|
||||
protected String getCircuitTypeLabel() {
|
||||
return "Exit";
|
||||
return "<span class=\"exit\">Exit</span>";
|
||||
}
|
||||
}
|
||||
|
@@ -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]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
@@ -134,8 +134,13 @@ public class StreamImpl implements Stream, DashboardRenderable {
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 1.2.2-0.2 */
|
||||
public boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -202,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…</i>");
|
||||
}
|
||||
writer.print(" target="+ streamTarget);
|
||||
writer.println("]");
|
||||
writer.println("</td></tr>");
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -15,6 +14,7 @@ import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.DirectoryDownloader;
|
||||
import com.subgraph.orchid.GuardEntry;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.path.CircuitNodeChooser;
|
||||
import com.subgraph.orchid.circuits.path.CircuitNodeChooser.WeightRule;
|
||||
@@ -47,7 +47,7 @@ public class EntryGuards {
|
||||
this.pendingProbes = new HashSet<GuardEntry>();
|
||||
this.bridges = new Bridges(config, directoryDownloader);
|
||||
this.lock = new Object();
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
this.executor = Threading.newPool("EntryGuards worker");
|
||||
}
|
||||
|
||||
public boolean isUsingBridges() {
|
||||
|
@@ -58,11 +58,16 @@ public class HiddenService {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
HiddenServiceCircuit getCircuit() {
|
||||
/**
|
||||
* As of 1.2.2, returns null if the previously set circuit is now closed.
|
||||
*/
|
||||
synchronized HiddenServiceCircuit getCircuit() {
|
||||
if (circuit != null && circuit.isClosed())
|
||||
circuit = null;
|
||||
return circuit;
|
||||
}
|
||||
|
||||
void setCircuit(HiddenServiceCircuit circuit) {
|
||||
synchronized void setCircuit(HiddenServiceCircuit circuit) {
|
||||
this.circuit = circuit;
|
||||
}
|
||||
|
||||
|
@@ -47,14 +47,16 @@ public class HiddenServiceManager {
|
||||
}
|
||||
|
||||
private synchronized HiddenServiceCircuit getCircuitTo(HiddenService hs) throws OpenFailedException {
|
||||
if(hs.getCircuit() == null) {
|
||||
HiddenServiceCircuit rv = hs.getCircuit();
|
||||
if(rv == null) {
|
||||
final HiddenServiceCircuit c = openCircuitTo(hs);
|
||||
if(c == null) {
|
||||
throw new OpenFailedException("Failed to open circuit to "+ hs.getOnionAddressForLogging());
|
||||
}
|
||||
hs.setCircuit(c);
|
||||
rv = c;
|
||||
}
|
||||
return hs.getCircuit();
|
||||
return rv;
|
||||
}
|
||||
|
||||
private HiddenServiceCircuit openCircuitTo(HiddenService hs) throws OpenFailedException {
|
||||
@@ -118,4 +120,4 @@ public class HiddenServiceManager {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -147,7 +147,7 @@ public class ConfigNodeFilter implements RouterFilter {
|
||||
}
|
||||
|
||||
private static RouterFilter createIdentityFilter(String s) {
|
||||
if(isIdentityString(s)) {
|
||||
if(!isIdentityString(s)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
final HexDigest identity = HexDigest.createFromString(s);
|
||||
|
425
src/java/com/subgraph/orchid/config/TorConfigImpl.java
Normal file
@@ -0,0 +1,425 @@
|
||||
package com.subgraph.orchid.config;
|
||||
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
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; // 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);
|
||||
maxClientCircuitsPending = 32;
|
||||
enforceDistinctSubnets = true;
|
||||
socksTimeout = toMS(2, TimeUnit.MINUTES);
|
||||
numEntryGuards = 3;
|
||||
useEntryGuards = true;
|
||||
longLivedPorts = toIntList("21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300");
|
||||
excludedNodes = Collections.emptyList();
|
||||
excludedExitNodes = Collections.emptyList();
|
||||
exitNodes = Collections.emptyList();
|
||||
entryNodes = Collections.emptyList();
|
||||
strictNodes = false;
|
||||
fascistFirewall = false;
|
||||
firewallPorts = toIntList("80,443");
|
||||
safeSocks = false;
|
||||
// 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>();
|
||||
}
|
||||
|
||||
private File dataDirectory;
|
||||
private long circuitBuildTimeout;
|
||||
private long circuitStreamTimeout;
|
||||
private long circuitIdleTimeout;
|
||||
private long newCircuitPeriod;
|
||||
private long maxCircuitDirtiness;
|
||||
private int maxClientCircuitsPending;
|
||||
private boolean enforceDistinctSubnets;
|
||||
private long socksTimeout;
|
||||
private int numEntryGuards;
|
||||
private boolean useEntryGuards;
|
||||
private List<Integer> longLivedPorts;
|
||||
private List<String> excludedNodes;
|
||||
private List<String> excludedExitNodes;
|
||||
private List<String> exitNodes;
|
||||
private List<String> entryNodes;
|
||||
private boolean strictNodes;
|
||||
private boolean fascistFirewall;
|
||||
private List<Integer> firewallPorts;
|
||||
private boolean safeSocks;
|
||||
private boolean safeLogging;
|
||||
private boolean warnUnsafeSocks;
|
||||
private boolean clientRejectInternalAddress;
|
||||
private boolean handshakeV3Enabled;
|
||||
private boolean handshakeV2Enabled;
|
||||
private TorConfigHSAuth hsAuth;
|
||||
private AutoBoolValue useNtorHandshake;
|
||||
private AutoBoolValue useMicrodescriptors;
|
||||
private boolean useBridges;
|
||||
private List<TorConfigBridgeLine> bridgeLines;
|
||||
|
||||
private static long toMS(long time, TimeUnit unit) {
|
||||
return TimeUnit.MILLISECONDS.convert(time, unit);
|
||||
}
|
||||
|
||||
private static File toFile(String path) {
|
||||
if(path.startsWith("~/")) {
|
||||
final File home = new File(System.getProperty("user.home"));
|
||||
return new File(home, path.substring(2));
|
||||
}
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
private static List<Integer> toIntList(String val) {
|
||||
final List<Integer> list = new ArrayList<Integer>();
|
||||
for(String s: val.split(",")) {
|
||||
list.add(Integer.parseInt(s));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<String> toStringList(String val) {
|
||||
final List<String> list = new ArrayList<String>();
|
||||
for(String s: val.split(",")) {
|
||||
list.add(s);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public TorConfigImpl() {
|
||||
resetDefaults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataDirectory() {
|
||||
return dataDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDataDirectory(File directory) {
|
||||
this.dataDirectory = directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCircuitBuildTimeout() {
|
||||
return circuitBuildTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCircuitBuildTimeout(long time, TimeUnit unit) {
|
||||
circuitBuildTimeout = toMS(time, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCircuitStreamTimeout() {
|
||||
return circuitStreamTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCircuitStreamTimeout(long time, TimeUnit unit) {
|
||||
circuitStreamTimeout = toMS(time, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCircuitIdleTimeout() {
|
||||
return circuitIdleTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCircuitIdleTimeout(long time, TimeUnit unit) {
|
||||
circuitIdleTimeout = toMS(time, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNewCircuitPeriod() {
|
||||
return newCircuitPeriod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNewCircuitPeriod(long time, TimeUnit unit) {
|
||||
newCircuitPeriod = toMS(time, unit);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxCircuitDirtiness() {
|
||||
return maxCircuitDirtiness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxCircuitDirtiness(long time, TimeUnit unit) {
|
||||
maxCircuitDirtiness = toMS(time, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxClientCircuitsPending() {
|
||||
return maxClientCircuitsPending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxClientCircuitsPending(int value) {
|
||||
maxClientCircuitsPending = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getEnforceDistinctSubnets() {
|
||||
return enforceDistinctSubnets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnforceDistinctSubnets(boolean value) {
|
||||
enforceDistinctSubnets = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSocksTimeout() {
|
||||
return socksTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSocksTimeout(long value) {
|
||||
socksTimeout = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumEntryGuards() {
|
||||
return numEntryGuards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNumEntryGuards(int value) {
|
||||
numEntryGuards = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseEntryGuards() {
|
||||
return useEntryGuards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseEntryGuards(boolean value) {
|
||||
useEntryGuards = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getLongLivedPorts() {
|
||||
return longLivedPorts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLongLivedPorts(List<Integer> ports) {
|
||||
longLivedPorts = ports;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExcludeNodes() {
|
||||
return excludedNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExcludeNodes(List<String> nodes) {
|
||||
excludedNodes = nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExcludeExitNodes() {
|
||||
return excludedExitNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExcludeExitNodes(List<String> nodes) {
|
||||
excludedExitNodes = nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExitNodes() {
|
||||
return exitNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExitNodes(List<String> nodes) {
|
||||
exitNodes = nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getEntryNodes() {
|
||||
return entryNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryNodes(List<String> nodes) {
|
||||
entryNodes = nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getStrictNodes() {
|
||||
return strictNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrictNodes(boolean value) {
|
||||
strictNodes = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFascistFirewall() {
|
||||
return fascistFirewall;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFascistFirewall(boolean value) {
|
||||
fascistFirewall = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getFirewallPorts() {
|
||||
return firewallPorts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirewallPorts(List<Integer> ports) {
|
||||
firewallPorts = ports;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSafeSocks() {
|
||||
return safeSocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSafeSocks(boolean value) {
|
||||
safeSocks = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSafeLogging() {
|
||||
return safeLogging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSafeLogging(boolean value) {
|
||||
safeLogging = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getWarnUnsafeSocks() {
|
||||
return warnUnsafeSocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWarnUnsafeSocks(boolean value) {
|
||||
warnUnsafeSocks = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getClientRejectInternalAddress() {
|
||||
return clientRejectInternalAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientRejectInternalAddress(boolean value) {
|
||||
clientRejectInternalAddress = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getHandshakeV3Enabled() {
|
||||
return handshakeV3Enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandshakeV3Enabled(boolean value) {
|
||||
handshakeV3Enabled = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getHandshakeV2Enabled() {
|
||||
return handshakeV2Enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandshakeV2Enabled(boolean value) {
|
||||
handshakeV2Enabled = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HSDescriptorCookie getHidServAuth(String key) {
|
||||
return hsAuth.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHidServAuth(String key, String value) {
|
||||
hsAuth.add(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoBoolValue getUseNTorHandshake() {
|
||||
return useNtorHandshake;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseNTorHandshake(AutoBoolValue value) {
|
||||
useNtorHandshake = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoBoolValue getUseMicrodescriptors() {
|
||||
return useMicrodescriptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseMicrodescriptors(AutoBoolValue value) {
|
||||
useMicrodescriptors = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseBridges() {
|
||||
return useBridges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseBridges(boolean value) {
|
||||
useBridges = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TorConfigBridgeLine> getBridges() {
|
||||
return bridgeLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBridge(IPv4Address address, int port) {
|
||||
bridgeLines.add(new TorConfigBridgeLine(address, port, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBridge(IPv4Address address, int port, HexDigest fingerprint) {
|
||||
bridgeLines.add(new TorConfigBridgeLine(address, port, fingerprint));
|
||||
}
|
||||
}
|
@@ -6,29 +6,29 @@ import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.TorConfig.AutoBoolValue;
|
||||
import com.subgraph.orchid.TorConfig.ConfigVarType;
|
||||
//import com.subgraph.orchid.TorConfig.ConfigVarType;
|
||||
|
||||
public class TorConfigParser {
|
||||
|
||||
public Object parseValue(String value, ConfigVarType type) {
|
||||
public Object parseValue(String value, String type) {
|
||||
switch(type) {
|
||||
case BOOLEAN:
|
||||
case "BOOLEAN":
|
||||
return Boolean.parseBoolean(value);
|
||||
case INTEGER:
|
||||
case "INTEGER":
|
||||
return Integer.parseInt(value);
|
||||
case INTERVAL:
|
||||
case "INTERVAL":
|
||||
return parseIntervalValue(value);
|
||||
case PATH:
|
||||
case "PATH":
|
||||
return parseFileValue(value);
|
||||
case PORTLIST:
|
||||
case "PORTLIST":
|
||||
return parseIntegerList(value);
|
||||
case STRING:
|
||||
case "STRING":
|
||||
return value;
|
||||
case STRINGLIST:
|
||||
case "STRINGLIST":
|
||||
return parseCSV(value);
|
||||
case AUTOBOOL:
|
||||
case "AUTOBOOL":
|
||||
return parseAutoBool(value);
|
||||
case HS_AUTH:
|
||||
case "HS_AUTH":
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
@@ -1,199 +0,0 @@
|
||||
package com.subgraph.orchid.config;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.TorConfig.ConfigVar;
|
||||
import com.subgraph.orchid.TorConfig.ConfigVarType;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
public class TorConfigProxy implements InvocationHandler {
|
||||
|
||||
private final Map<String, Object> configValues;
|
||||
private final List<TorConfigBridgeLine> bridges;
|
||||
private final TorConfigParser parser;
|
||||
|
||||
public TorConfigProxy() {
|
||||
this.configValues = new HashMap<String, Object>();
|
||||
this.bridges = new ArrayList<TorConfigBridgeLine>();
|
||||
this.configValues.put("Bridges", bridges);
|
||||
this.parser = new TorConfigParser();
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if(method.getName().startsWith("set")) {
|
||||
invokeSetMethod(method, args);
|
||||
return null;
|
||||
} else if(method.getName().startsWith("get")) {
|
||||
if(args == null) {
|
||||
return invokeGetMethod(method);
|
||||
} else {
|
||||
return invokeGetMethodWithArgs(method, args);
|
||||
}
|
||||
} else if(method.getName().startsWith("add")) {
|
||||
invokeAddMethod(method, args);
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
void invokeSetMethod(Method method, Object[] args) {
|
||||
final String name = getVariableNameForMethod(method);
|
||||
final ConfigVar annotation = getAnnotationForVariable(name);
|
||||
if(annotation != null && annotation.type() == ConfigVarType.INTERVAL) {
|
||||
setIntervalValue(name, args);
|
||||
} else {
|
||||
configValues.put(name, args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIntervalValue(String varName, Object[] args) {
|
||||
if(!(args[0] instanceof Long && args[1] instanceof TimeUnit)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
final long time = (Long) args[0];
|
||||
final TimeUnit unit = (TimeUnit) args[1];
|
||||
final TorConfigInterval interval = new TorConfigInterval(time, unit);
|
||||
configValues.put(varName, interval);
|
||||
}
|
||||
|
||||
|
||||
private Object invokeGetMethodWithArgs(Method method, Object[] args) {
|
||||
final String varName = getVariableNameForMethod(method);
|
||||
if(getVariableType(varName) == ConfigVarType.HS_AUTH) {
|
||||
return invokeHSAuthGet(varName, args);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private Object invokeGetMethod(Method method) {
|
||||
final String varName = getVariableNameForMethod(method);
|
||||
final Object value = getVariableValue(varName);
|
||||
|
||||
if(value instanceof TorConfigInterval) {
|
||||
final TorConfigInterval interval = (TorConfigInterval) value;
|
||||
return interval.getMilliseconds();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private Object invokeHSAuthGet(String varName, Object[] args) {
|
||||
if(!(args[0] instanceof String)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
final TorConfigHSAuth hsAuth = getHSAuth(varName);
|
||||
return hsAuth.get((String) args[0]);
|
||||
}
|
||||
|
||||
private void invokeAddMethod(Method method, Object[] args) {
|
||||
final String name = getVariableNameForMethod(method);
|
||||
final ConfigVarType type = getVariableType(name);
|
||||
switch(type) {
|
||||
case HS_AUTH:
|
||||
invokeHSAuthAdd(name, args);
|
||||
break;
|
||||
|
||||
case BRIDGE_LINE:
|
||||
invokeBridgeAdd(args);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("addX configuration methods only supported for HS_AUTH or BRIDGE_LINE type");
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeBridgeAdd(Object[] args) {
|
||||
if(args.length >= 2 && (args[0] instanceof IPv4Address) && (args[1] instanceof Integer)) {
|
||||
if(args.length == 2) {
|
||||
bridges.add(new TorConfigBridgeLine((IPv4Address)args[0], (Integer)args[1], null));
|
||||
return;
|
||||
} else if(args.length == 3 && (args[2] instanceof HexDigest)) {
|
||||
bridges.add(new TorConfigBridgeLine((IPv4Address) args[0], (Integer) args[1], (HexDigest) args[2]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private void invokeHSAuthAdd(String name, Object[] args) {
|
||||
if(!(args.length == 2 && (args[0] instanceof String) && (args[1] instanceof String))) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
final TorConfigHSAuth hsAuth = getHSAuth(name);
|
||||
hsAuth.add((String)args[0], (String)args[1]);
|
||||
}
|
||||
|
||||
private TorConfigHSAuth getHSAuth(String keyName) {
|
||||
if(!configValues.containsKey(keyName)) {
|
||||
configValues.put(keyName, new TorConfigHSAuth());
|
||||
}
|
||||
return (TorConfigHSAuth) configValues.get(keyName);
|
||||
}
|
||||
|
||||
private Object getVariableValue(String varName) {
|
||||
if(configValues.containsKey(varName)) {
|
||||
return configValues.get(varName);
|
||||
} else {
|
||||
return getDefaultVariableValue(varName);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDefaultVariableValue(String varName) {
|
||||
final String defaultValue = getDefaultValueString(varName);
|
||||
final ConfigVarType type = getVariableType(varName);
|
||||
if(defaultValue == null || type == null) {
|
||||
return null;
|
||||
}
|
||||
return parser.parseValue(defaultValue, type);
|
||||
}
|
||||
|
||||
private String getDefaultValueString(String varName) {
|
||||
final ConfigVar var = getAnnotationForVariable(varName);
|
||||
if(var == null) {
|
||||
return null;
|
||||
} else {
|
||||
return var.defaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigVarType getVariableType(String varName) {
|
||||
if("Bridge".equals(varName)) {
|
||||
return ConfigVarType.BRIDGE_LINE;
|
||||
}
|
||||
|
||||
final ConfigVar var = getAnnotationForVariable(varName);
|
||||
if(var == null) {
|
||||
return null;
|
||||
} else {
|
||||
return var.type();
|
||||
}
|
||||
}
|
||||
|
||||
private String getVariableNameForMethod(Method method) {
|
||||
final String methodName = method.getName();
|
||||
if(methodName.startsWith("get") || methodName.startsWith("set") || methodName.startsWith("add")) {
|
||||
return methodName.substring(3);
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private ConfigVar getAnnotationForVariable(String varName) {
|
||||
final String getName = "get"+ varName;
|
||||
for(Method m: TorConfig.class.getDeclaredMethods()) {
|
||||
if(getName.equals(m.getName())) {
|
||||
return m.getAnnotation(TorConfig.ConfigVar.class);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ import com.subgraph.orchid.ConnectionFailedException;
|
||||
import com.subgraph.orchid.ConnectionHandshakeException;
|
||||
import com.subgraph.orchid.ConnectionTimeoutException;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.TorInitializationTracker;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
||||
@@ -65,7 +66,7 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
|
||||
|
||||
private final ConcurrentMap<Router, Future<ConnectionImpl>> activeConnections = new ConcurrentHashMap<Router, Future<ConnectionImpl>>();
|
||||
private final ConnectionSocketFactory factory = new ConnectionSocketFactory();
|
||||
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ScheduledExecutorService scheduledExecutor = Threading.newSingleThreadScheduledPool("Connection");
|
||||
|
||||
private final TorConfig config;
|
||||
private final TorInitializationTracker initializationTracker;
|
||||
@@ -94,6 +95,9 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
|
||||
logger.warning("Exception closing connection: "+ e.getCause());
|
||||
}
|
||||
} else {
|
||||
// FIXME this doesn't close the socket, so the connection task lingers
|
||||
// A proper fix would require maintaining pending connections in a separate
|
||||
// collection.
|
||||
f.cancel(true);
|
||||
}
|
||||
}
|
||||
@@ -101,11 +105,18 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
|
||||
scheduledExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
|
||||
public Connection getConnectionTo(Router router, boolean isDirectoryConnection) throws InterruptedException, ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException {
|
||||
if(isClosed) {
|
||||
throw new IllegalStateException("ConnectionCache has been closed");
|
||||
// I2P prevent logs at shutdown
|
||||
//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 {
|
||||
@@ -157,23 +168,37 @@ 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)]");
|
||||
String useMds = String.valueOf(this.config.getUseMicrodescriptors());
|
||||
writer.print("<hr>\n<table id=\"conncache\" width=\"100%\">\n");
|
||||
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true")) {
|
||||
writer.print("<tr><th colspan=\"3\" align=\"left\">Connection Cache</th></tr>\n<tr class=\"subtitle\">" +
|
||||
"<th align=\"left\" width=\"33%\">Guard Node <span title=\"Circuits available for immediate use\">(Circuits)</span></th>" +
|
||||
"<th align=\"left\" width=\"34%\">Idle</th>" +
|
||||
"<th align=\"left\" width=\"33%\" title=\"Estimated node bandwidth\">Bandwidth</th>");
|
||||
} else {
|
||||
writer.println("[Connection Cache]");
|
||||
writer.print("<tr><th colspan=\"4\" 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>");
|
||||
}
|
||||
writer.println();
|
||||
writer.print("</tr>\n");
|
||||
}
|
||||
|
||||
List<Connection> getActiveConnections() {
|
||||
|
@@ -21,8 +21,7 @@ import com.subgraph.orchid.ConnectionIOException;
|
||||
|
||||
public class ConnectionHandshakeV3 extends ConnectionHandshake {
|
||||
|
||||
private X509Certificate linkCertificate;
|
||||
private X509Certificate identityCertificate;
|
||||
private X509Certificate linkCertificate, identityCertificate, signing, tls, cross;
|
||||
|
||||
ConnectionHandshakeV3(ConnectionImpl connection, SSLSocket socket) {
|
||||
super(connection, socket);
|
||||
@@ -40,12 +39,15 @@ public class ConnectionHandshakeV3 extends ConnectionHandshake {
|
||||
void recvCerts() throws ConnectionHandshakeException {
|
||||
final Cell cell = expectCell(Cell.CERTS);
|
||||
final int ncerts = cell.getByte();
|
||||
if(ncerts != 2) {
|
||||
throw new ConnectionHandshakeException("Expecting 2 certificates and got "+ ncerts);
|
||||
if (ncerts != 2 && ncerts != 5) {
|
||||
throw new ConnectionHandshakeException("Expecting 2 or 5 certificates and got "+ ncerts);
|
||||
}
|
||||
|
||||
linkCertificate = null;
|
||||
identityCertificate = null;
|
||||
signing = null;
|
||||
tls = null;
|
||||
cross = null;
|
||||
|
||||
for(int i = 0; i < ncerts; i++) {
|
||||
int type = cell.getByte();
|
||||
@@ -53,6 +55,12 @@ public class ConnectionHandshakeV3 extends ConnectionHandshake {
|
||||
linkCertificate = testAndReadCertificate(cell, linkCertificate, "Link (type = 1)");
|
||||
} else if(type == 2) {
|
||||
identityCertificate = testAndReadCertificate(cell, identityCertificate, "Identity (type = 2)");
|
||||
} else if(type == 4) {
|
||||
signing = testAndReadCertificate(cell, signing, "Ed25519 signing key (type = 4)");
|
||||
} else if(type == 5) {
|
||||
tls = testAndReadCertificate(cell, tls, "TLS Link certificate (type = 5)");
|
||||
} else if(type == 7) {
|
||||
cross = testAndReadCertificate(cell, cross, "RSA Identity cross verification (type = 7)");
|
||||
} else {
|
||||
throw new ConnectionHandshakeException("Unexpected certificate type = "+ type + " in CERTS cell");
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -27,6 +28,7 @@ import com.subgraph.orchid.ConnectionHandshakeException;
|
||||
import com.subgraph.orchid.ConnectionIOException;
|
||||
import com.subgraph.orchid.ConnectionTimeoutException;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.TorException;
|
||||
@@ -61,21 +63,27 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
private boolean isConnected;
|
||||
private volatile boolean isClosed;
|
||||
private final Thread readCellsThread;
|
||||
private final Object connectLock = new Object();
|
||||
private final ReentrantLock connectLock = Threading.lock("connect");
|
||||
private final ReentrantLock circuitsLock = Threading.lock("circuits");
|
||||
private final ReentrantLock outputLock = Threading.lock("output");
|
||||
private final AtomicLong lastActivity = new AtomicLong();
|
||||
|
||||
private static final AtomicLong num = new AtomicLong();
|
||||
|
||||
|
||||
public ConnectionImpl(TorConfig config, SSLSocket socket, Router router, TorInitializationTracker tracker, boolean isDirectoryConnection) {
|
||||
this.config = config;
|
||||
this.socket = socket;
|
||||
this.router = router;
|
||||
this.circuitMap = new HashMap<Integer, Circuit>();
|
||||
// TODO use thread pool from Threading?
|
||||
this.readCellsThread = new Thread(createReadCellsRunnable());
|
||||
this.readCellsThread.setDaemon(true);
|
||||
this.readCellsThread.setName("ReadCells-" + num.getAndIncrement());
|
||||
this.connectionControlCells = new LinkedBlockingQueue<Cell>();
|
||||
this.initializationTracker = tracker;
|
||||
this.isDirectoryConnection = isDirectoryConnection;
|
||||
initializeCurrentCircuitId();
|
||||
this.readCellsThread.setName("ConnectionImpl-" + num.getAndIncrement());
|
||||
}
|
||||
|
||||
private void initializeCurrentCircuitId() {
|
||||
@@ -92,13 +100,16 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
}
|
||||
|
||||
public int bindCircuit(Circuit circuit) {
|
||||
synchronized(circuitMap) {
|
||||
circuitsLock.lock();
|
||||
try {
|
||||
while(circuitMap.containsKey(currentId))
|
||||
incrementNextId();
|
||||
final int id = currentId;
|
||||
incrementNextId();
|
||||
circuitMap.put(id, circuit);
|
||||
return id;
|
||||
} finally {
|
||||
circuitsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +120,8 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
}
|
||||
|
||||
void connect() throws ConnectionFailedException, ConnectionTimeoutException, ConnectionHandshakeException {
|
||||
synchronized (connectLock) {
|
||||
connectLock.lock();
|
||||
try {
|
||||
if(isConnected) {
|
||||
return;
|
||||
}
|
||||
@@ -128,6 +140,8 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
throw new ConnectionFailedException(e.getMessage());
|
||||
}
|
||||
isConnected = true;
|
||||
} finally {
|
||||
connectLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,14 +185,17 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
throw new ConnectionIOException("Cannot send cell because connection is not connected");
|
||||
}
|
||||
updateLastActivity();
|
||||
synchronized(output) {
|
||||
outputLock.lock();
|
||||
try {
|
||||
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());
|
||||
}
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,32 +293,45 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
}
|
||||
|
||||
private void processRelayCell(Cell cell) {
|
||||
synchronized(circuitMap) {
|
||||
final Circuit circuit = circuitMap.get(cell.getCircuitId());
|
||||
Circuit circuit;
|
||||
circuitsLock.lock();
|
||||
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;
|
||||
}
|
||||
circuit.deliverRelayCell(cell);
|
||||
} finally {
|
||||
circuitsLock.unlock();
|
||||
}
|
||||
|
||||
circuit.deliverRelayCell(cell);
|
||||
}
|
||||
|
||||
private void processControlCell(Cell cell) {
|
||||
synchronized(circuitMap) {
|
||||
final Circuit circuit = circuitMap.get(cell.getCircuitId());
|
||||
if(circuit != null) {
|
||||
circuit.deliverControlCell(cell);
|
||||
}
|
||||
Circuit circuit;
|
||||
circuitsLock.lock();
|
||||
try {
|
||||
circuit = circuitMap.get(cell.getCircuitId());
|
||||
} finally {
|
||||
circuitsLock.unlock();
|
||||
}
|
||||
|
||||
if(circuit != null) {
|
||||
circuit.deliverControlCell(cell);
|
||||
}
|
||||
}
|
||||
|
||||
void idleCloseCheck() {
|
||||
synchronized (circuitMap) {
|
||||
circuitsLock.lock();
|
||||
try {
|
||||
final boolean needClose = (!isClosed && circuitMap.isEmpty() && getIdleMilliseconds() > CONNECTION_IDLE_TIMEOUT);
|
||||
if(needClose) {
|
||||
logger.fine("Closing connection to "+ this +" on idle timeout");
|
||||
closeSocket();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
circuitsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,26 +347,96 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
|
||||
}
|
||||
|
||||
public void removeCircuit(Circuit circuit) {
|
||||
synchronized(circuitMap) {
|
||||
circuitsLock.lock();
|
||||
try {
|
||||
circuitMap.remove(circuit.getCircuitId());
|
||||
} finally {
|
||||
circuitsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "!" + router.getNickname() + "!";
|
||||
return "[" + router.getNickname() + "]";
|
||||
}
|
||||
|
||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
||||
final int circuitCount;
|
||||
synchronized (circuitMap) {
|
||||
circuitsLock.lock();
|
||||
try {
|
||||
circuitCount = circuitMap.size();
|
||||
} finally {
|
||||
circuitsLock.unlock();
|
||||
}
|
||||
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>");
|
||||
String useMds = String.valueOf(this.config.getUseMicrodescriptors());
|
||||
long bw = router.getObservedBandwidth();
|
||||
long bwEst = router.getEstimatedBandwidth();
|
||||
writer.print("<span class=\"nowrap\">");
|
||||
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true")) {
|
||||
if (bwEst > 0 && bwEst < 2048)
|
||||
writer.print(bwEst + " KB/s");
|
||||
else if (bwEst >= 2048)
|
||||
writer.print((bwEst / 1024) + " MB/s");
|
||||
else
|
||||
writer.print("unknown");
|
||||
} else {
|
||||
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>");
|
||||
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true")) {
|
||||
writer.print("<!-- useMicroDescriptors=TRUE, not showing uptime column -->");
|
||||
} else {
|
||||
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>");
|
||||
}
|
||||
writer.print("</tr>\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,8 @@ import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import net.i2p.util.I2PSSLSocketFactory;
|
||||
|
||||
import com.subgraph.orchid.TorException;
|
||||
|
||||
public class ConnectionSocketFactory {
|
||||
@@ -66,7 +68,9 @@ public class ConnectionSocketFactory {
|
||||
SSLSocket createSocket() {
|
||||
try {
|
||||
final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
|
||||
socket.setEnabledCipherSuites(MANDATORY_CIPHERS);
|
||||
// Use I2P lib to blacklist weak protocols and ciphers
|
||||
// We do not detect the the "Fixed Ciphersuite List", see tor-spec section 2.1
|
||||
I2PSSLSocketFactory.setProtocolsAndCiphers(socket);
|
||||
socket.setUseClientMode(true);
|
||||
return socket;
|
||||
} catch (IOException e) {
|
||||
|
@@ -7,9 +7,9 @@ import java.net.Socket;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.misc.GuardedBy;
|
||||
|
||||
@@ -37,7 +37,7 @@ public class Dashboard implements DashboardRenderable, DashboardRenderer {
|
||||
public Dashboard() {
|
||||
renderables = new CopyOnWriteArrayList<DashboardRenderable>();
|
||||
renderables.add(this);
|
||||
executor = Executors.newCachedThreadPool();
|
||||
executor = Threading.newPool("Dashboard worker");
|
||||
listeningPort = chooseListeningPort();
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,6 @@ package com.subgraph.orchid.directory;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -12,6 +11,7 @@ import java.util.logging.Logger;
|
||||
import com.subgraph.orchid.Descriptor;
|
||||
import com.subgraph.orchid.DirectoryStore;
|
||||
import com.subgraph.orchid.DirectoryStore.CacheFile;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParser;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingResult;
|
||||
@@ -23,7 +23,9 @@ public abstract class DescriptorCache <T extends Descriptor> {
|
||||
private final DescriptorCacheData<T> data;
|
||||
|
||||
private final DirectoryStore store;
|
||||
private final ScheduledExecutorService rebuildExecutor = Executors.newScheduledThreadPool(1);
|
||||
private final ScheduledExecutorService rebuildExecutor =
|
||||
Threading.newScheduledPool("DescriptorCache rebuild worker");
|
||||
|
||||
private final CacheFile cacheFile;
|
||||
private final CacheFile journalFile;
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
package com.subgraph.orchid.directory;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -129,14 +129,45 @@ public class DirectoryImpl implements Directory {
|
||||
}
|
||||
boolean useMicrodescriptors = config.getUseMicrodescriptors() != AutoBoolValue.FALSE;
|
||||
last = System.currentTimeMillis();
|
||||
logger.info("Loading certificates");
|
||||
loadCertificates(store.loadCacheFile(CacheFile.CERTIFICATES));
|
||||
// shouldn't have to do this, but this is the easiest way to make it work
|
||||
File f0 = new File(config.getDataDirectory(), CacheFile.CERTIFICATES.getFilename());
|
||||
long m0 = f0.lastModified();
|
||||
if (m0 > 0 && (last - m0) > 60*24*60*60*1000L) {
|
||||
logger.info("Certificates not cached or too old");
|
||||
} else {
|
||||
logger.info("Loading certificates");
|
||||
loadCertificates(store.loadCacheFile(CacheFile.CERTIFICATES));
|
||||
}
|
||||
logElapsed();
|
||||
|
||||
logger.info("Loading consensus");
|
||||
loadConsensus(store.loadCacheFile(useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS));
|
||||
//loadConsensus(store.loadCacheFile(useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS));
|
||||
CacheFile consensusCacheFile = useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS;
|
||||
File consensusFile = new File(config.getDataDirectory(), consensusCacheFile.getFilename());
|
||||
long consensusDate = consensusFile.lastModified();
|
||||
long now = System.currentTimeMillis();
|
||||
long age = now - consensusDate;
|
||||
ByteBuffer consensus;
|
||||
if (age < 24*60*60*1000L) {
|
||||
logger.info("Loading consensus");
|
||||
consensus = store.loadCacheFile(consensusCacheFile);
|
||||
} else {
|
||||
logger.info("Consensus not cached or too old");
|
||||
consensus = ByteBuffer.allocate(0);
|
||||
}
|
||||
loadConsensus(consensus);
|
||||
logElapsed();
|
||||
|
||||
// shouldn't have to do this, but this is the easiest way to make it work
|
||||
File f1 = new File(config.getDataDirectory(),
|
||||
(useMicrodescriptors ? CacheFile.MICRODESCRIPTOR_CACHE : CacheFile.DESCRIPTOR_CACHE).getFilename());
|
||||
File f2 = new File(config.getDataDirectory(),
|
||||
(useMicrodescriptors ? CacheFile.MICRODESCRIPTOR_JOURNAL : CacheFile.DESCRIPTOR_JOURNAL).getFilename());
|
||||
long m1 = f1.lastModified();
|
||||
long m2 = f2.lastModified();
|
||||
if (m1 > 0 && (now - m1) > 2*24*60*60*1000L)
|
||||
f1.delete();
|
||||
if (m2 > 0 && (now - m2) > 2*24*60*60*1000L)
|
||||
f2.delete();
|
||||
if(!useMicrodescriptors) {
|
||||
logger.info("Loading descriptors");
|
||||
basicDescriptorCache.initialLoad();
|
||||
|
@@ -67,6 +67,18 @@ public class DirectoryServerImpl extends RouterImpl implements DirectoryServer {
|
||||
return v3Ident;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/geo-gs/Orchid/commit/22beeae1b881707491addaba6a7654e9de9f9db1
|
||||
*/
|
||||
public KeyCertificate getCertificateByAuthority(HexDigest fingerprint){
|
||||
for(KeyCertificate kc: getCertificates()) {
|
||||
if(kc.getAuthorityFingerprint().equals(fingerprint)) {
|
||||
return kc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public KeyCertificate getCertificateByFingerprint(HexDigest fingerprint) {
|
||||
for(KeyCertificate kc: getCertificates()) {
|
||||
if(kc.getAuthoritySigningKey().getFingerprint().equals(fingerprint)) {
|
||||
|
@@ -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) {
|
||||
|
@@ -14,20 +14,22 @@ import com.subgraph.orchid.directory.parsing.DocumentParsingHandler;
|
||||
/*
|
||||
* This class contains the hardcoded 'bootstrap' directory authority
|
||||
* server information.
|
||||
* https://github.com/torproject/tor/blob/master/src/app/config/auth_dirs.inc
|
||||
*/
|
||||
public class TrustedAuthorities {
|
||||
|
||||
private final static String[] dirServers = {
|
||||
"authority moria1 orport=9101 no-v2 v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
|
||||
"authority moria1 orport=9101 v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
|
||||
"authority tor26 v1 orport=443 v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D",
|
||||
"authority dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
|
||||
"authority Tonga orport=443 bridge no-v2 82.94.251.203:80 4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D",
|
||||
"authority turtles orport=9090 no-v2 v3ident=27B6B5996C426270A5C95488AA5BCEB6BCC86956 76.73.17.194:9030 F397 038A DC51 3361 35E7 B80B D99C A384 4360 292B",
|
||||
"authority dannenberg orport=443 no-v2 v3ident=585769C78764D58426B8B52B6651A5A71137189A 193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
|
||||
"authority urras orport=80 no-v2 v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417",
|
||||
"authority maatuska orport=80 no-v2 v3ident=49015F787433103580E3B66A1707A00E60F2D15B 171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",
|
||||
"authority Faravahar orport=443 no-v2 v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 154.35.32.5:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC",
|
||||
"authority gabelmoo orport=443 no-v2 v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281",
|
||||
"authority dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 45.66.33.45:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
|
||||
"authority longclaw orport=443 v3ident=23D15D965BC35114467363C165C4F724B64B4F66 199.58.81.140:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145",
|
||||
"authority dannenberg orport=443 v3ident=0232AF901C31A04EE9848595AF9BB7620D4C5B2E 193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
|
||||
"authority maatuska orport=80 v3ident=49015F787433103580E3B66A1707A00E60F2D15B 171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",
|
||||
"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",
|
||||
// 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>();
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -262,7 +262,9 @@ public class ConsensusDocumentImpl implements ConsensusDocument {
|
||||
}
|
||||
|
||||
private SignatureStatus verifySignatureForTrustedAuthority(DirectoryServer trustedAuthority, DirectorySignature signature) {
|
||||
final KeyCertificate certificate = trustedAuthority.getCertificateByFingerprint(signature.getSigningKeyDigest());
|
||||
// https://github.com/geo-gs/Orchid/commit/22beeae1b881707491addaba6a7654e9de9f9db1
|
||||
//final KeyCertificate certificate = trustedAuthority.getCertificateByFingerprint(signature.getSigningKeyDigest());
|
||||
final KeyCertificate certificate = trustedAuthority.getCertificateByAuthority(trustedAuthority.getV3Identity());
|
||||
if(certificate == null) {
|
||||
logger.fine("Missing certificate for signing key: "+ signature.getSigningKeyDigest());
|
||||
addRequiredCertificateForSignature(signature);
|
||||
|
@@ -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;
|
||||
|
@@ -6,7 +6,6 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -14,6 +13,7 @@ import com.subgraph.orchid.ConsensusDocument;
|
||||
import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.DirectoryDownloader;
|
||||
import com.subgraph.orchid.KeyCertificate;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.TorConfig.AutoBoolValue;
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
@@ -31,7 +31,7 @@ public class DirectoryDownloadTask implements Runnable {
|
||||
private final TorRandom random;
|
||||
private final DescriptorProcessor descriptorProcessor;
|
||||
|
||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
private final ExecutorService executor = Threading.newPool("DirectoryDownloadTask worker");
|
||||
|
||||
private volatile boolean isDownloadingCertificates;
|
||||
private volatile boolean isDownloadingConsensus;
|
||||
|
@@ -80,7 +80,7 @@ public class HttpConnection {
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
if(hostname == null) {
|
||||
if(hostname != null) {
|
||||
return hostname;
|
||||
} else {
|
||||
return "(none)";
|
||||
|
@@ -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),
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -26,10 +26,16 @@ public class OrchidSocketFactory extends SocketFactory {
|
||||
this.exceptionOnLocalBind = exceptionOnLocalBind;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return createSocketInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException,
|
||||
UnknownHostException {
|
||||
return createOrchidSocket(host, port);
|
||||
final Socket s = createSocketInstance();
|
||||
return connectOrchidSocket(s, host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +49,8 @@ public class OrchidSocketFactory extends SocketFactory {
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port) throws IOException {
|
||||
return createOrchidSocket(address.getHostAddress(), port);
|
||||
final Socket s = createSocketInstance();
|
||||
return connectOrchidSocket(s, address.getHostAddress(), port);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,8 +62,7 @@ public class OrchidSocketFactory extends SocketFactory {
|
||||
return createSocket(address, port);
|
||||
}
|
||||
|
||||
private Socket createOrchidSocket(String host, int port) throws IOException {
|
||||
final Socket s = createSocketInstance();
|
||||
private Socket connectOrchidSocket(Socket s, String host, int port) throws IOException {
|
||||
final SocketAddress endpoint = InetSocketAddress.createUnresolved(host, port);
|
||||
s.connect(endpoint);
|
||||
return s;
|
||||
|
@@ -1,5 +1,10 @@
|
||||
package com.subgraph.orchid.sockets;
|
||||
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorClient;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -13,16 +18,12 @@ import java.net.SocketImpl;
|
||||
import java.net.SocketOptions;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.TorClient;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
public class OrchidSocketImpl extends SocketImpl {
|
||||
|
||||
private final TorClient torClient;
|
||||
private final Object streamLock = new Object();
|
||||
|
||||
private Lock streamLock = Threading.lock("stream");
|
||||
private Stream stream;
|
||||
|
||||
OrchidSocketImpl(TorClient torClient) {
|
||||
@@ -31,7 +32,7 @@ public class OrchidSocketImpl extends SocketImpl {
|
||||
}
|
||||
|
||||
public void setOption(int optID, Object value) throws SocketException {
|
||||
throw new UnsupportedOperationException();
|
||||
// Ignored.
|
||||
}
|
||||
|
||||
public Object getOption(int optID) throws SocketException {
|
||||
@@ -53,13 +54,16 @@ public class OrchidSocketImpl extends SocketImpl {
|
||||
|
||||
@Override
|
||||
protected void connect(String host, int port) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
SocketAddress endpoint =
|
||||
InetSocketAddress.createUnresolved(host, port);
|
||||
connect(endpoint, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connect(InetAddress address, int port) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
SocketAddress endpoint =
|
||||
InetSocketAddress.createUnresolved(address.getHostAddress(), port);
|
||||
connect(endpoint, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,20 +86,36 @@ public class OrchidSocketImpl extends SocketImpl {
|
||||
}
|
||||
|
||||
private void doConnect(String host, int port) throws IOException {
|
||||
synchronized(streamLock) {
|
||||
if(stream != null) {
|
||||
throw new SocketException("Already connected");
|
||||
}
|
||||
try {
|
||||
stream = torClient.openExitStreamTo(host, port);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new SocketException("connect() interrupted");
|
||||
} catch (TimeoutException e) {
|
||||
throw new SocketTimeoutException();
|
||||
} catch (OpenFailedException e) {
|
||||
throw new ConnectException(e.getMessage());
|
||||
}
|
||||
Stream stream;
|
||||
|
||||
// Try to avoid holding the stream lock here whilst calling into torclient to avoid accidental inversions.
|
||||
|
||||
streamLock.lock();
|
||||
stream = this.stream;
|
||||
streamLock.unlock();
|
||||
|
||||
if (stream != null)
|
||||
throw new SocketException("Already connected");
|
||||
|
||||
try {
|
||||
stream = torClient.openExitStreamTo(host, port);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new SocketException("connect() interrupted");
|
||||
} catch (TimeoutException e) {
|
||||
throw new SocketTimeoutException();
|
||||
} catch (OpenFailedException e) {
|
||||
throw new ConnectException(e.getMessage());
|
||||
}
|
||||
|
||||
streamLock.lock();
|
||||
if (this.stream != null) {
|
||||
// Raced with another concurrent call.
|
||||
streamLock.unlock();
|
||||
stream.close();
|
||||
} else {
|
||||
this.stream = stream;
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,44 +134,41 @@ public class OrchidSocketImpl extends SocketImpl {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private Stream getStream() throws IOException {
|
||||
streamLock.lock();
|
||||
try {
|
||||
if (stream == null)
|
||||
throw new IOException("Not connected");
|
||||
return stream;
|
||||
} finally {
|
||||
streamLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
synchronized (streamLock) {
|
||||
if(stream == null) {
|
||||
throw new IOException("Not connected");
|
||||
}
|
||||
return stream.getInputStream();
|
||||
}
|
||||
return getStream().getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
synchronized (streamLock) {
|
||||
if(stream == null) {
|
||||
throw new IOException("Not connected");
|
||||
}
|
||||
return stream.getOutputStream();
|
||||
}
|
||||
return getStream().getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int available() throws IOException {
|
||||
synchronized(streamLock) {
|
||||
if(stream == null) {
|
||||
throw new IOException("Not connected");
|
||||
}
|
||||
return stream.getInputStream().available();
|
||||
}
|
||||
return getStream().getInputStream().available();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void close() throws IOException {
|
||||
synchronized (streamLock) {
|
||||
if(stream != null) {
|
||||
stream.close();
|
||||
stream = null;
|
||||
}
|
||||
}
|
||||
Stream toClose;
|
||||
streamLock.lock();
|
||||
toClose = this.stream;
|
||||
this.stream = null;
|
||||
streamLock.unlock();
|
||||
if (toClose != null)
|
||||
toClose.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,10 +177,10 @@ public class OrchidSocketImpl extends SocketImpl {
|
||||
}
|
||||
|
||||
protected void shutdownInput() throws IOException {
|
||||
//throw new IOException("Method not implemented!");
|
||||
}
|
||||
//throw new IOException("Method not implemented!");
|
||||
}
|
||||
|
||||
protected void shutdownOutput() throws IOException {
|
||||
//throw new IOException("Method not implemented!");
|
||||
}
|
||||
//throw new IOException("Method not implemented!");
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.CircuitManager;
|
||||
import com.subgraph.orchid.SocksPortListener;
|
||||
import com.subgraph.orchid.Threading;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.TorException;
|
||||
|
||||
@@ -28,7 +29,7 @@ public class SocksPortListenerImpl implements SocksPortListener {
|
||||
public SocksPortListenerImpl(TorConfig config, CircuitManager circuitManager) {
|
||||
this.config = config;
|
||||
this.circuitManager = circuitManager;
|
||||
executor = Executors.newCachedThreadPool();
|
||||
executor = Threading.newPool("Socks");
|
||||
}
|
||||
|
||||
public void addListeningPort(int port) {
|
||||
|
@@ -1,309 +0,0 @@
|
||||
package com.subgraph.orchid.xmlrpc;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import org.apache.xmlrpc.XmlRpcException;
|
||||
import org.apache.xmlrpc.XmlRpcRequest;
|
||||
import org.apache.xmlrpc.client.XmlRpcClient;
|
||||
import org.apache.xmlrpc.client.XmlRpcClientException;
|
||||
import org.apache.xmlrpc.client.XmlRpcHttpClientConfig;
|
||||
import org.apache.xmlrpc.client.XmlRpcHttpTransport;
|
||||
import org.apache.xmlrpc.client.XmlRpcHttpTransportException;
|
||||
import org.apache.xmlrpc.client.XmlRpcLiteHttpTransport;
|
||||
import org.apache.xmlrpc.common.XmlRpcStreamRequestConfig;
|
||||
import org.apache.xmlrpc.util.HttpUtil;
|
||||
import org.apache.xmlrpc.util.LimitedInputStream;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.sockets.AndroidSSLSocketFactory;
|
||||
|
||||
public class OrchidXmlRpcTransport extends XmlRpcHttpTransport {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(OrchidXmlRpcTransport.class.getName());
|
||||
|
||||
private final SocketFactory socketFactory;
|
||||
private final SSLContext sslContext;
|
||||
|
||||
private SSLSocketFactory sslSocketFactory;
|
||||
|
||||
public OrchidXmlRpcTransport(XmlRpcClient pClient, SocketFactory socketFactory, SSLContext sslContext) {
|
||||
super(pClient, userAgent);
|
||||
this.socketFactory = socketFactory;
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
public synchronized SSLSocketFactory getSSLSocketFactory() {
|
||||
if(sslSocketFactory == null) {
|
||||
sslSocketFactory = createSSLSocketFactory();
|
||||
}
|
||||
return sslSocketFactory;
|
||||
}
|
||||
|
||||
private SSLSocketFactory createSSLSocketFactory() {
|
||||
if(Tor.isAndroidRuntime()) {
|
||||
return createAndroidSSLSocketFactory();
|
||||
}
|
||||
if(sslContext == null) {
|
||||
return (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
} else {
|
||||
return sslContext.getSocketFactory();
|
||||
}
|
||||
}
|
||||
|
||||
private SSLSocketFactory createAndroidSSLSocketFactory() {
|
||||
if(sslContext == null) {
|
||||
try {
|
||||
return new AndroidSSLSocketFactory();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.severe("Failed to create default ssl context");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return new AndroidSSLSocketFactory(sslContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected Socket newSocket(boolean pSSL, String pHostName, int pPort) throws UnknownHostException, IOException {
|
||||
final Socket s = socketFactory.createSocket(pHostName, pPort);
|
||||
if(pSSL) {
|
||||
return getSSLSocketFactory().createSocket(s, pHostName, pPort, true);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String userAgent = USER_AGENT + " (Lite HTTP Transport)";
|
||||
private boolean ssl;
|
||||
private String hostname;
|
||||
private String host;
|
||||
private int port;
|
||||
private String uri;
|
||||
private Socket socket;
|
||||
private OutputStream output;
|
||||
private InputStream input;
|
||||
private final Map<String, Object> headers = new HashMap<String, Object>();
|
||||
private boolean responseGzipCompressed = false;
|
||||
private XmlRpcHttpClientConfig config;
|
||||
|
||||
|
||||
public Object sendRequest(XmlRpcRequest pRequest) throws XmlRpcException {
|
||||
config = (XmlRpcHttpClientConfig) pRequest.getConfig();
|
||||
URL url = config.getServerURL();
|
||||
ssl = "https".equals(url.getProtocol());
|
||||
hostname = url.getHost();
|
||||
int p = url.getPort();
|
||||
port = p < 1 ? 80 : p;
|
||||
String u = url.getFile();
|
||||
uri = (u == null || "".equals(u)) ? "/" : u;
|
||||
host = port == 80 ? hostname : hostname + ":" + port;
|
||||
headers.put("Host", host);
|
||||
return super.sendRequest(pRequest);
|
||||
}
|
||||
|
||||
protected void setRequestHeader(String pHeader, String pValue) {
|
||||
Object value = headers.get(pHeader);
|
||||
if (value == null) {
|
||||
headers.put(pHeader, pValue);
|
||||
} else {
|
||||
List<Object> list;
|
||||
if (value instanceof String) {
|
||||
list = new ArrayList<Object>();
|
||||
list.add((String)value);
|
||||
headers.put(pHeader, list);
|
||||
} else {
|
||||
list = (List<Object>) value;
|
||||
}
|
||||
list.add(pValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected void close() throws XmlRpcClientException {
|
||||
IOException e = null;
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException ex) {
|
||||
e = ex;
|
||||
}
|
||||
}
|
||||
if (output != null) {
|
||||
try {
|
||||
output.close();
|
||||
} catch (IOException ex) {
|
||||
if (e != null) {
|
||||
e = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ex) {
|
||||
if (e != null) {
|
||||
e = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e != null) {
|
||||
throw new XmlRpcClientException("Failed to close connection: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private OutputStream getOutputStream() throws XmlRpcException {
|
||||
try {
|
||||
final int retries = 3;
|
||||
final int delayMillis = 100;
|
||||
|
||||
for (int tries = 0; ; tries++) {
|
||||
try {
|
||||
socket = newSocket(ssl, hostname, port);
|
||||
output = new BufferedOutputStream(socket.getOutputStream()){
|
||||
/** Closing the output stream would close the whole socket, which we don't want,
|
||||
* because the don't want until the request is processed completely.
|
||||
* A close will later occur within
|
||||
* {@link XmlRpcLiteHttpTransport#close()}.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
flush();
|
||||
if(!(socket instanceof SSLSocket)) {
|
||||
socket.shutdownOutput();
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
} catch (ConnectException e) {
|
||||
if (tries >= retries) {
|
||||
throw new XmlRpcException("Failed to connect to "
|
||||
+ hostname + ":" + port + ": " + e.getMessage(), e);
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(delayMillis);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sendRequestHeaders(output);
|
||||
return output;
|
||||
} catch (IOException e) {
|
||||
throw new XmlRpcException("Failed to open connection to "
|
||||
+ hostname + ":" + port + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private byte[] toHTTPBytes(String pValue) throws UnsupportedEncodingException {
|
||||
return pValue.getBytes("US-ASCII");
|
||||
}
|
||||
|
||||
private void sendHeader(OutputStream pOut, String pKey, String pValue) throws IOException {
|
||||
pOut.write(toHTTPBytes(pKey + ": " + pValue + "\r\n"));
|
||||
}
|
||||
|
||||
private void sendRequestHeaders(OutputStream pOut) throws IOException {
|
||||
pOut.write(("POST " + uri + " HTTP/1.0\r\n").getBytes("US-ASCII"));
|
||||
for (Iterator iter = headers.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
String key = (String) entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof String) {
|
||||
sendHeader(pOut, key, (String) value);
|
||||
} else {
|
||||
List list = (List) value;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
sendHeader(pOut, key, (String) list.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
pOut.write(toHTTPBytes("\r\n"));
|
||||
}
|
||||
|
||||
protected boolean isResponseGzipCompressed(XmlRpcStreamRequestConfig pConfig) {
|
||||
return responseGzipCompressed;
|
||||
}
|
||||
|
||||
protected InputStream getInputStream() throws XmlRpcException {
|
||||
final byte[] buffer = new byte[2048];
|
||||
try {
|
||||
// If reply timeout specified, set the socket timeout accordingly
|
||||
if (config.getReplyTimeout() != 0)
|
||||
socket.setSoTimeout(config.getReplyTimeout());
|
||||
input = new BufferedInputStream(socket.getInputStream());
|
||||
// start reading server response headers
|
||||
String line = HttpUtil.readLine(input, buffer);
|
||||
StringTokenizer tokens = new StringTokenizer(line);
|
||||
tokens.nextToken(); // Skip HTTP version
|
||||
String statusCode = tokens.nextToken();
|
||||
String statusMsg = tokens.nextToken("\n\r");
|
||||
final int code;
|
||||
try {
|
||||
code = Integer.parseInt(statusCode);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new XmlRpcClientException("Server returned invalid status code: "
|
||||
+ statusCode + " " + statusMsg, null);
|
||||
}
|
||||
if (code < 200 || code > 299) {
|
||||
throw new XmlRpcHttpTransportException(code, statusMsg);
|
||||
}
|
||||
int contentLength = -1;
|
||||
for (;;) {
|
||||
line = HttpUtil.readLine(input, buffer);
|
||||
if (line == null || "".equals(line)) {
|
||||
break;
|
||||
}
|
||||
line = line.toLowerCase();
|
||||
if (line.startsWith("content-length:")) {
|
||||
contentLength = Integer.parseInt(line.substring("content-length:".length()).trim());
|
||||
} else if (line.startsWith("content-encoding:")) {
|
||||
responseGzipCompressed = HttpUtil.isUsingGzipEncoding(line.substring("content-encoding:".length()));
|
||||
}
|
||||
}
|
||||
InputStream result;
|
||||
if (contentLength == -1) {
|
||||
result = input;
|
||||
} else {
|
||||
result = new LimitedInputStream(input, contentLength);
|
||||
}
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new XmlRpcClientException("Failed to read server response: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isUsingByteArrayOutput(XmlRpcHttpClientConfig pConfig) {
|
||||
boolean result = super.isUsingByteArrayOutput(pConfig);
|
||||
if (!result) {
|
||||
throw new IllegalStateException("The Content-Length header is required with HTTP/1.0, and HTTP/1.1 is unsupported by the Lite HTTP Transport.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void writeRequest(ReqWriter pWriter) throws XmlRpcException, IOException, SAXException {
|
||||
pWriter.write(getOutputStream());
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
package com.subgraph.orchid.xmlrpc;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.xmlrpc.client.XmlRpcClient;
|
||||
import org.apache.xmlrpc.client.XmlRpcTransport;
|
||||
import org.apache.xmlrpc.client.XmlRpcTransportFactory;
|
||||
import com.subgraph.orchid.TorClient;
|
||||
import com.subgraph.orchid.sockets.OrchidSocketFactory;
|
||||
|
||||
public class OrchidXmlRpcTransportFactory implements XmlRpcTransportFactory {
|
||||
private final XmlRpcClient client;
|
||||
private final SSLContext sslContext;
|
||||
private final SocketFactory socketFactory;
|
||||
|
||||
public OrchidXmlRpcTransportFactory(XmlRpcClient client, TorClient torClient) {
|
||||
this(client, torClient, null);
|
||||
}
|
||||
|
||||
public OrchidXmlRpcTransportFactory(XmlRpcClient client, TorClient torClient, SSLContext sslContext) {
|
||||
this.client = client;
|
||||
this.socketFactory = new OrchidSocketFactory(torClient);
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
public XmlRpcTransport getTransport() {
|
||||
return new OrchidXmlRpcTransport(client, socketFactory, sslContext);
|
||||
}
|
||||
}
|
@@ -8,7 +8,9 @@ package net.i2p.orchid;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -17,13 +19,14 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.subgraph.orchid.TorClient;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import static com.subgraph.orchid.TorConfig.ConfigVarType.*;
|
||||
// import static com.subgraph.orchid.TorConfig.ConfigVarType.*;
|
||||
import com.subgraph.orchid.TorConfig.AutoBoolValue;
|
||||
import com.subgraph.orchid.TorInitializationListener;
|
||||
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.*;
|
||||
@@ -92,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");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,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);
|
||||
@@ -112,18 +116,21 @@ 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,
|
||||
// unlimited strength policy files not installed
|
||||
changeState(START_FAILED);
|
||||
changeState(START_FAILED, t);
|
||||
throw t;
|
||||
}
|
||||
if (_mgr != null)
|
||||
_mgr.register(this);
|
||||
if (_mgr != null) {
|
||||
// Don't register until initializationCompleted()
|
||||
//_mgr.register(this);
|
||||
// RouterAppManager registers its own shutdown hook
|
||||
else
|
||||
} else {
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,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) {
|
||||
@@ -200,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);
|
||||
@@ -219,6 +226,8 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
|
||||
} catch (Exception e) {
|
||||
IOException ioe = new IOException("connect error");
|
||||
ioe.initCause(e);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error connecting to " + host + ':' + port + "\n* Reason:" + ioe.getMessage());
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
@@ -240,79 +249,79 @@ 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();
|
||||
String v = (String) e.getValue();
|
||||
if (k.equals("bridges")) {
|
||||
// unimplemented in parser, will throw IAE
|
||||
List<TorConfigBridgeLine> list = (List<TorConfigBridgeLine>) tcp.parseValue(v, BRIDGE_LINE);
|
||||
List<TorConfigBridgeLine> list = (List<TorConfigBridgeLine>) tcp.parseValue(v, "BRIDGE_LINE");
|
||||
for (TorConfigBridgeLine tcbl : list) {
|
||||
tc.addBridge(tcbl.getAddress(), tcbl.getPort(), tcbl.getFingerprint());
|
||||
}
|
||||
} else if (k.equals("circuitBuildTimeout")) {
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
|
||||
tc.setCircuitBuildTimeout(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
|
||||
} else if (k.equals("circuitIdleTimeout")) {
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
|
||||
tc.setCircuitIdleTimeout(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
|
||||
} else if (k.equals("circuitStreamTimeout")) {
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
|
||||
tc.setCircuitStreamTimeout(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
|
||||
} else if (k.equals("clientRejectInternalAddress")) {
|
||||
tc.setClientRejectInternalAddress((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setClientRejectInternalAddress((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("enforceDistinctSubnets")) {
|
||||
tc.setEnforceDistinctSubnets((Boolean) tcp.parseValue(v, INTEGER));
|
||||
tc.setEnforceDistinctSubnets((Boolean) tcp.parseValue(v, "INTEGER"));
|
||||
} else if (k.equals("numEntryGuards")) {
|
||||
tc.setNumEntryGuards((Integer) tcp.parseValue(v, INTEGER));
|
||||
tc.setNumEntryGuards((Integer) tcp.parseValue(v, "INTEGER"));
|
||||
} else if (k.equals("entryNodes")) {
|
||||
tc.setEntryNodes((List<String>) tcp.parseValue(v, STRINGLIST));
|
||||
tc.setEntryNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
|
||||
} else if (k.equals("excludeExitNodes")) {
|
||||
tc.setExcludeExitNodes((List<String>) tcp.parseValue(v, STRINGLIST));
|
||||
tc.setExcludeExitNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
|
||||
} else if (k.equals("excludeNodes")) {
|
||||
tc.setExcludeNodes((List<String>) tcp.parseValue(v, STRINGLIST));
|
||||
tc.setExcludeNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
|
||||
} else if (k.equals("exitNodes")) {
|
||||
tc.setExitNodes((List<String>) tcp.parseValue(v, STRINGLIST));
|
||||
tc.setExitNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
|
||||
} else if (k.equals("fascistFirewall")) {
|
||||
tc.setFascistFirewall((Boolean) tcp.parseValue(v, INTEGER));
|
||||
tc.setFascistFirewall((Boolean) tcp.parseValue(v, "INTEGER"));
|
||||
} else if (k.equals("firewallPorts")) {
|
||||
tc.setFirewallPorts((List<Integer>) tcp.parseValue(v, PORTLIST));
|
||||
tc.setFirewallPorts((List<Integer>) tcp.parseValue(v, "PORTLIST"));
|
||||
} else if (k.equals("handshakeV2Enabled")) {
|
||||
tc.setHandshakeV2Enabled((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setHandshakeV2Enabled((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("handshakeV3Enabled")) {
|
||||
tc.setHandshakeV3Enabled((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setHandshakeV3Enabled((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("longLivedPorts")) {
|
||||
tc.setLongLivedPorts((List<Integer>) tcp.parseValue(v, PORTLIST));
|
||||
tc.setLongLivedPorts((List<Integer>) tcp.parseValue(v, "PORTLIST"));
|
||||
} else if (k.equals("maxCircuitDirtiness")) {
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
|
||||
tc.setMaxCircuitDirtiness(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
|
||||
} else if (k.equals("maxClientCircuitsPending")) {
|
||||
tc.setMaxClientCircuitsPending((Integer) tcp.parseValue(v, INTEGER));
|
||||
tc.setMaxClientCircuitsPending((Integer) tcp.parseValue(v, "INTEGER"));
|
||||
} else if (k.equals("newCircuitPeriod")) {
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
|
||||
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
|
||||
tc.setNewCircuitPeriod(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
|
||||
} else if (k.equals("safeLogging")) {
|
||||
tc.setSafeLogging((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setSafeLogging((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("safeSocks")) {
|
||||
tc.setSafeSocks((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setSafeSocks((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("strictNodes")) {
|
||||
tc.setStrictNodes((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setStrictNodes((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("useBridges")) {
|
||||
tc.setUseBridges((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
tc.setUseBridges((Boolean) tcp.parseValue(v, "BOOLEAN"));
|
||||
} else if (k.equals("useMicrodescriptors")) {
|
||||
tc.setUseMicrodescriptors((AutoBoolValue) tcp.parseValue(v, AUTOBOOL));
|
||||
tc.setUseMicrodescriptors((AutoBoolValue) tcp.parseValue(v, "AUTOBOOL"));
|
||||
} else if (k.equals("useNTorHandshake")) {
|
||||
tc.setUseNTorHandshake((AutoBoolValue) tcp.parseValue(v, AUTOBOOL));
|
||||
tc.setUseNTorHandshake((AutoBoolValue) tcp.parseValue(v, "AUTOBOOL"));
|
||||
} else if (k.equals("warnUnsafeSocks")) {
|
||||
tc.setWarnUnsafeSocks((Boolean) tcp.parseValue(v, BOOLEAN));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,8 +330,12 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
|
||||
if (_client == null)
|
||||
return;
|
||||
// can't get to TorConfig's Dashboard from here so make a new one
|
||||
// FIXME strip HTML
|
||||
(new Dashboard()).renderComponent(out, 0xff, _client.getCircuitManager());
|
||||
StringWriter sw = new StringWriter(1024);
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
(new Dashboard()).renderComponent(pw, 0xff, _client.getCircuitManager());
|
||||
pw.close();
|
||||
out.write(sw.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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…", "")
|
||||
.replaceAll("…", "...")
|
||||
.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) {
|
||||
|
@@ -15,9 +15,13 @@ import com.subgraph.orchid.TorClient;
|
||||
public class TorStreamSocket extends Socket {
|
||||
|
||||
private final Stream _stream;
|
||||
//private final String _host;
|
||||
private final int _port;
|
||||
|
||||
public TorStreamSocket(TorClient client, String host, int port) throws Exception {
|
||||
_stream = client.openExitStreamTo(host, port);
|
||||
//_host = host;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -32,7 +36,7 @@ public class TorStreamSocket extends Socket {
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return false;
|
||||
return _stream.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,7 +54,8 @@ public class TorStreamSocket extends Socket {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// everything below here unsupported
|
||||
// several below here unsupported
|
||||
|
||||
/** @deprecated unsupported */
|
||||
@Override
|
||||
public void bind(SocketAddress endpoint) {
|
||||
@@ -66,46 +71,71 @@ public class TorStreamSocket extends Socket {
|
||||
public void connect(SocketAddress endpoint, int timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public SocketChannel getChannel() {
|
||||
throw new UnsupportedOperationException();
|
||||
return null;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* We could return InetAddress.getByName(), but that's not
|
||||
* necessarily the actual IP of the socket.
|
||||
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public InetAddress getInetAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @deprecated unsupported */
|
||||
@Override
|
||||
public boolean getKeepAlive() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public InetAddress getLocalAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
return null;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return 0 since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
throw new UnsupportedOperationException();
|
||||
return 0;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public SocketAddress getLocalSocketAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
return null;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return false since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public boolean getOOBInline() {
|
||||
throw new UnsupportedOperationException();
|
||||
return false;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return remote port since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public int getPort() {
|
||||
throw new UnsupportedOperationException();
|
||||
return _port;
|
||||
}
|
||||
|
||||
/** @deprecated unsupported */
|
||||
@Override
|
||||
public int getReceiveBufferSize() {
|
||||
@@ -116,11 +146,15 @@ public class TorStreamSocket extends Socket {
|
||||
public SocketAddress getRemoteSocketAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return false since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public boolean getReuseAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @deprecated unsupported */
|
||||
@Override
|
||||
public int getSendBufferSize() {
|
||||
@@ -136,46 +170,69 @@ public class TorStreamSocket extends Socket {
|
||||
public boolean getTcpNoDelay() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return 0 since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public int getTrafficClass() {
|
||||
throw new UnsupportedOperationException();
|
||||
return 0;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* @return true since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public boolean isBound() {
|
||||
throw new UnsupportedOperationException();
|
||||
return true;
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* Supported since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
throw new UnsupportedOperationException();
|
||||
return !_stream.isClosed();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* Supported since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public boolean isInputShutdown() {
|
||||
throw new UnsupportedOperationException();
|
||||
return _stream.isClosed();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* Supported since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public boolean isOutputShutdown() {
|
||||
throw new UnsupportedOperationException();
|
||||
return _stream.isClosed();
|
||||
}
|
||||
|
||||
/** @deprecated unsupported */
|
||||
@Override
|
||||
public void sendUrgentData(int data) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* Does nothing since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public void setKeepAlive(boolean on) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
public void setKeepAlive(boolean on) {}
|
||||
|
||||
/**
|
||||
* On is unsupported
|
||||
* @throws UnsupportedOperationException if on is true
|
||||
*/
|
||||
@Override
|
||||
public void setOOBInline(boolean on) {
|
||||
throw new UnsupportedOperationException();
|
||||
if (on)
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/** @deprecated unsupported */
|
||||
@Override
|
||||
public void setReceiveBufferSize(int size) {
|
||||
@@ -206,15 +263,21 @@ public class TorStreamSocket extends Socket {
|
||||
public void setTrafficClass(int cize) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* Closes both sides since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public void shutdownInput() {
|
||||
throw new UnsupportedOperationException();
|
||||
public void shutdownInput() throws IOException {
|
||||
close();
|
||||
}
|
||||
/** @deprecated unsupported */
|
||||
|
||||
/**
|
||||
* Closes both sides since 1.2.2-0.2, prior to that threw UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public void shutdownOutput() {
|
||||
throw new UnsupportedOperationException();
|
||||
public void shutdownOutput() throws IOException {
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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></center></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,82 +130,144 @@ 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(_("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=\"34%\">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 " +
|
||||
"<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> \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 <Firewall Ports></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 <Use Entry Guards> 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 <bridges> 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");
|
||||
String useMds = String.valueOf(tc.getUseMicrodescriptors());
|
||||
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true"))
|
||||
buf.append("<style>#conncache .nickname:hover::after {display: none !important;}</style>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** translate */
|
||||
private String _(String s) {
|
||||
private String _t(String s) {
|
||||
return Translate.getString(s, _context, BUNDLE);
|
||||
}
|
||||
|
||||
/** translate */
|
||||
private String _(String s, Object o) {
|
||||
private String _t(String s, Object o) {
|
||||
return Translate.getString(s, o, _context, BUNDLE);
|
||||
}
|
||||
|
||||
/** translate */
|
||||
private String _(String s, Object o, Object o2) {
|
||||
private String _t(String s, Object o, Object o2) {
|
||||
return Translate.getString(s, o, o2, _context, BUNDLE);
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
|