Orchid 1.0.0 from website:
http://www.subgraph.com/orchid.html , "Orchid is licensed under a three-clause BSD license." No modifications. Selected Apache XMLRPC 3.1.3 jars from: http://ws.apache.org/xmlrpc/download.html Apache 2.0 license Todo: https, socks Set data directory Logging to router logs? jsp status/control
This commit is contained in:
3
LICENSE.txt
Normal file
3
LICENSE.txt
Normal file
@ -0,0 +1,3 @@
|
||||
I2P license: TBD
|
||||
|
||||
Bundled software: see plugins/licenses
|
3
README.txt
Normal file
3
README.txt
Normal file
@ -0,0 +1,3 @@
|
||||
This is a bundling of the orchid Java Tor client into an I2P plugin.
|
||||
|
||||
See http://www.subgraph.com/orchid.html for more information.
|
80
build.xml
Normal file
80
build.xml
Normal file
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project basedir="." default="all" name="orchid">
|
||||
|
||||
<target name="all" depends="clean,plugin" />
|
||||
|
||||
<property name="xmlrpc.version" value="3.1.3" />
|
||||
|
||||
<target name="war" >
|
||||
<ant dir="src" target="build" />
|
||||
</target>
|
||||
|
||||
<target name="plugin" depends="war">
|
||||
<delete file="plugin/i2ptunnel.config" />
|
||||
<!-- get version number -->
|
||||
<buildnumber file="scripts/build.number" />
|
||||
<property name="release.number" value="1.0.0-0.1" />
|
||||
|
||||
<!-- make the update xpi2p -->
|
||||
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
|
||||
<copy file="README.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" />
|
||||
</exec>
|
||||
<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/orchid.jar.pack" />
|
||||
<arg value="src/build/orchid.jar" />
|
||||
</exec>
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-g" />
|
||||
<arg value="plugin/console/webapps/orchid.war.pack" />
|
||||
<arg value="src/build/orchid.war.jar" />
|
||||
</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" >
|
||||
<arg value="plugin" />
|
||||
</exec>
|
||||
<move file="orchid.xpi2p" tofile="orchid-update.xpi2p" overwrite="true" />
|
||||
|
||||
<!-- make the install xpi2p -->
|
||||
<copy file="scripts/i2ptunnel.config" 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="version=${release.number}-b${build.number}" />
|
||||
</exec>
|
||||
<exec executable="scripts/makeplugin.sh" failonerror="true" >
|
||||
<arg value="plugin" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="distclean" depends="clean" />
|
||||
|
||||
<target name="clean" >
|
||||
<ant dir="src" target="clean" />
|
||||
<delete file="plugin/i2ptunnel.config" />
|
||||
<delete file="plugin/plugin.config" />
|
||||
<delete file="plugin/lib/orchid.jar.pack" />
|
||||
<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="orchid.xpi2p" />
|
||||
<delete file="orchid-update.xpi2p" />
|
||||
</target>
|
||||
|
||||
</project>
|
BIN
lib/xmlrpc-client-3.1.3.jar
Normal file
BIN
lib/xmlrpc-client-3.1.3.jar
Normal file
Binary file not shown.
BIN
lib/xmlrpc-common-3.1.3.jar
Normal file
BIN
lib/xmlrpc-common-3.1.3.jar
Normal file
Binary file not shown.
6
plugin/clients.config
Normal file
6
plugin/clients.config
Normal file
@ -0,0 +1,6 @@
|
||||
clientApp.0.main=net.i2p.orchid.OrchidController
|
||||
clientApp.0.name=Orchid
|
||||
clientApp.0.args=$PLUGIN
|
||||
clientApp.0.delay=60
|
||||
clientApp.0.startOnLoad=true
|
||||
clientApp.0.classpath=$PLUGIN/lib/orchid.jar,$PLUGIN/lib/xmlrpc-commons.jar,$PLUGIND/lib/xmlrpc-client.jar
|
3
plugin/licenses/LICENSE-Orchid.txt
Normal file
3
plugin/licenses/LICENSE-Orchid.txt
Normal file
@ -0,0 +1,3 @@
|
||||
According to http://www.subgraph.com/orchid.html ,
|
||||
|
||||
"Orchid is licensed under a three-clause BSD license."
|
202
plugin/licenses/LICENSE-xmlrpc.txt
Normal file
202
plugin/licenses/LICENSE-xmlrpc.txt
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
5
plugin/licenses/NOTICE.txt
Normal file
5
plugin/licenses/NOTICE.txt
Normal file
@ -0,0 +1,5 @@
|
||||
Apache XML-RPC
|
||||
Copyright 1999-2009 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
1
plugin/webapps.config
Normal file
1
plugin/webapps.config
Normal file
@ -0,0 +1 @@
|
||||
webapps.orchid.classpath=$I2P/lib/orchid.jar,$I2P/lib/xmlrpc-commons.jar,$PLUGIN/lib/xmlrpc-client.jar
|
104
scripts/makeplugin.sh
Executable file
104
scripts/makeplugin.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# basic packaging up of a plugin
|
||||
#
|
||||
# usage: makeplugin.sh plugindir
|
||||
#
|
||||
# zzz 2010-02
|
||||
#
|
||||
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
|
||||
|
||||
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 ]
|
||||
then
|
||||
echo "You must have a $PLUGINDIR directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OPWD=$PWD
|
||||
cd $PLUGINDIR
|
||||
|
||||
if [ ! -f $PC ]
|
||||
then
|
||||
echo "You must have a $PC file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -q '^signer=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a signer in $PC"
|
||||
echo 'For example signer=joe@mail.i2p'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -q '^name=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a plugin name in $PC"
|
||||
echo 'For example name=foo'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -q '^version=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a version in $PC"
|
||||
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
|
||||
|
||||
# add our Base64 key
|
||||
grep -v '^key=' $PC > $PCT
|
||||
B64KEY=`cat $B64KEYFILE`
|
||||
echo "key=$B64KEY" >> $PCT || exit 1
|
||||
mv $PCT $PC
|
||||
|
||||
# zip it
|
||||
zip -r $OPWD/plugin.zip * -x \*.jar || 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
|
||||
|
||||
# sign it
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || 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
|
||||
rm -rf logs/
|
||||
|
||||
echo -n 'Plugin created: '
|
||||
wc -c $XPI2P
|
14
scripts/plugin.config
Normal file
14
scripts/plugin.config
Normal file
@ -0,0 +1,14 @@
|
||||
name=orchid
|
||||
signer=zzz-plugin@mail.i2p
|
||||
consoleLinkName=Orchid
|
||||
consoleLinkURL=/orchid
|
||||
description=Tor Outproxy
|
||||
author=zzz
|
||||
updateURL=http://stats.i2p/i2p/plugins/orchid-update.xpi2p
|
||||
websiteURL=http://zzz.i2p/forums/16
|
||||
license=BSD
|
||||
min-jetty-version=7
|
||||
# TODO higher for hook
|
||||
min-i2p-version=0.9.9
|
||||
# ???
|
||||
min-java-version=1.5
|
1
scripts/runtor
Executable file
1
scripts/runtor
Executable file
@ -0,0 +1 @@
|
||||
java -cp ../src/build/orchid.jar com.subgraph.orchid.TorClient
|
99
src/build.xml
Normal file
99
src/build.xml
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2psnark">
|
||||
<property name="i2pbase" value="../../i2p.i2p.zzz.outproxy"/>
|
||||
<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}/org.mortbay.jetty.jar"/>
|
||||
<pathelement location="${jettylib}/jasper-runtime.jar" />
|
||||
<pathelement location="${jettylib}/javax.servlet.jar" />
|
||||
<pathelement location="${jettylib}/jetty-util.jar" />
|
||||
<pathelement location="${jettylib}/jetty-xml.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" />
|
||||
</path>
|
||||
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="jar, war" />
|
||||
<target name="builddep">
|
||||
</target>
|
||||
|
||||
<property name="javac.compilerargs" value="" />
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./java"
|
||||
debug="true" deprecation="on" source="1.5" target="1.5"
|
||||
destdir="./build/obj"
|
||||
includeAntRuntime="false"
|
||||
classpath="${i2plib}/i2p.jar:${i2plib}/i2ptunnel.jar:${i2plib}/i2psnark.jar:${i2plib}/mstreaming.jar:${i2plib}/systray.jar:${jettylib}/org.mortbay.jetty.jar:${jettylib}/jetty-util.jar:${jettylib}/jetty-xml.jar:../lib/xmlrpc-client-${xmlrpc.version}.jar:../lib/xmlrpc-common-${xmlrpc.version}.jar" >
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="build/orchid.jar" basedir="./build/obj" includes="**/*.class" >
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="precompilejsp" depends="compile" >
|
||||
<mkdir dir="build" />
|
||||
<mkdir dir="build/war/WEB-INF/classes" />
|
||||
<path id="jspcp">
|
||||
<path refid="cp" />
|
||||
<pathelement location="build/obj" />
|
||||
</path>
|
||||
<java classname="org.apache.jasper.JspC" fork="true" classpathref="jspcp" failonerror="true">
|
||||
<arg value="-d" />
|
||||
<arg value="build/jspjava" />
|
||||
<arg value="-v" />
|
||||
<arg value="-p" />
|
||||
<arg value="net.i2p.orchid" />
|
||||
<arg value="-webinc" />
|
||||
<arg value="build/web-fragment.xml" />
|
||||
<arg value="-webapp" />
|
||||
<arg value="jsp/" />
|
||||
</java>
|
||||
|
||||
<javac
|
||||
debug="true"
|
||||
deprecation="on"
|
||||
source="1.5" target="1.5"
|
||||
destdir="build/war/WEB-INF/classes"
|
||||
srcdir="./build/jspjava"
|
||||
includes="**/*.java"
|
||||
includeAntRuntime="false"
|
||||
classpathref="jspcp"
|
||||
failonerror="true" />
|
||||
|
||||
<copy file="jsp/WEB-INF/web.xml" tofile="build/web.xml" />
|
||||
<loadfile property="jspc.web.fragment" srcfile="build/web-fragment.xml" />
|
||||
<replace file="build/web.xml">
|
||||
<replacefilter token="<!-- precompiled servlets -->" value="${jspc.web.fragment}" />
|
||||
</replace>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="war" depends="precompilejsp">
|
||||
<copy file="jsp/index.html" todir="build/war" />
|
||||
<war destfile="build/orchid.war.jar" webxml="build/web.xml">
|
||||
<fileset dir="build/war" />
|
||||
</war>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
|
||||
<target name="cleandep" depends="clean">
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
</target>
|
||||
</project>
|
8
src/java/com/subgraph/orchid/BridgeRouter.java
Normal file
8
src/java/com/subgraph/orchid/BridgeRouter.java
Normal file
@ -0,0 +1,8 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
|
||||
public interface BridgeRouter extends Router {
|
||||
void setIdentity(HexDigest identity);
|
||||
void setDescriptor(RouterDescriptor descriptor);
|
||||
}
|
230
src/java/com/subgraph/orchid/Cell.java
Normal file
230
src/java/com/subgraph/orchid/Cell.java
Normal file
@ -0,0 +1,230 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
public interface Cell {
|
||||
/** Command constant for a PADDING type cell. */
|
||||
final static int PADDING = 0;
|
||||
|
||||
/** Command constant for a CREATE type cell. */
|
||||
final static int CREATE = 1;
|
||||
|
||||
/** Command constant for a CREATED type cell. */
|
||||
final static int CREATED = 2;
|
||||
|
||||
/** Command constant for a RELAY type cell. */
|
||||
final static int RELAY = 3;
|
||||
|
||||
/** Command constant for a DESTROY type cell. */
|
||||
final static int DESTROY = 4;
|
||||
|
||||
/** Command constant for a CREATE_FAST type cell. */
|
||||
final static int CREATE_FAST = 5;
|
||||
|
||||
/** Command constant for a CREATED_FAST type cell. */
|
||||
final static int CREATED_FAST = 6;
|
||||
|
||||
/** Command constant for a VERSIONS type cell. */
|
||||
final static int VERSIONS = 7;
|
||||
|
||||
/** Command constant for a NETINFO type cell. */
|
||||
final static int NETINFO = 8;
|
||||
|
||||
/** Command constant for a RELAY_EARLY type cell. */
|
||||
final static int RELAY_EARLY = 9;
|
||||
|
||||
final static int VPADDING = 128;
|
||||
final static int CERTS = 129;
|
||||
final static int AUTH_CHALLENGE = 130;
|
||||
final static int AUTHENTICATE = 131;
|
||||
final static int AUTHORIZE = 132;
|
||||
|
||||
final static int ERROR_NONE = 0;
|
||||
final static int ERROR_PROTOCOL = 1;
|
||||
final static int ERROR_INTERNAL = 2;
|
||||
final static int ERROR_REQUESTED = 3;
|
||||
final static int ERROR_HIBERNATING = 4;
|
||||
final static int ERROR_RESOURCELIMIT = 5;
|
||||
final static int ERROR_CONNECTFAILED = 6;
|
||||
final static int ERROR_OR_IDENTITY = 7;
|
||||
final static int ERROR_OR_CONN_CLOSED = 8;
|
||||
final static int ERROR_FINISHED = 9;
|
||||
final static int ERROR_TIMEOUT = 10;
|
||||
final static int ERROR_DESTROYED = 11;
|
||||
final static int ERROR_NOSUCHSERVICE = 12;
|
||||
|
||||
final static int ADDRESS_TYPE_HOSTNAME = 0x00;
|
||||
final static int ADDRESS_TYPE_IPV4 = 0x04;
|
||||
final static int ADRESS_TYPE_IPV6 = 0x06;
|
||||
|
||||
/**
|
||||
* The fixed size of a standard cell.
|
||||
*/
|
||||
final static int CELL_LEN = 512;
|
||||
|
||||
/**
|
||||
* The length of a standard cell header.
|
||||
*/
|
||||
final static int CELL_HEADER_LEN = 3;
|
||||
|
||||
/**
|
||||
* The header length for a variable length cell (ie: VERSIONS)
|
||||
*/
|
||||
final static int CELL_VAR_HEADER_LEN = 5;
|
||||
|
||||
/**
|
||||
* The length of the payload space in a standard cell.
|
||||
*/
|
||||
final static int CELL_PAYLOAD_LEN = CELL_LEN - CELL_HEADER_LEN;
|
||||
|
||||
/**
|
||||
* Return the circuit id field from this cell.
|
||||
*
|
||||
* @return The circuit id field of this cell.
|
||||
*/
|
||||
int getCircuitId();
|
||||
|
||||
/**
|
||||
* Return the command field from this cell.
|
||||
*
|
||||
* @return The command field of this cell.
|
||||
*/
|
||||
int getCommand();
|
||||
|
||||
/**
|
||||
* Set the internal pointer to the first byte after the cell header.
|
||||
*/
|
||||
void resetToPayload();
|
||||
|
||||
/**
|
||||
* Return the next byte from the cell and increment the internal pointer by one byte.
|
||||
*
|
||||
* @return The byte at the current pointer location.
|
||||
*/
|
||||
int getByte();
|
||||
|
||||
/**
|
||||
* Return the byte at the specified offset into the cell.
|
||||
*
|
||||
* @param index The cell offset.
|
||||
* @return The byte at the specified offset.
|
||||
*/
|
||||
int getByteAt(int index);
|
||||
|
||||
/**
|
||||
* Return the next 16-bit big endian value from the cell and increment the internal pointer by two bytes.
|
||||
*
|
||||
* @return The 16-bit short value at the current pointer location.
|
||||
*/
|
||||
int getShort();
|
||||
|
||||
/**
|
||||
* Return the 16-bit big endian value at the specified offset into the cell.
|
||||
*
|
||||
* @param index The cell offset.
|
||||
* @return The 16-bit short value at the specified offset.
|
||||
*/
|
||||
int getShortAt(int index);
|
||||
|
||||
/**
|
||||
* Return the next 32-bit big endian value from the cell and increment the internal pointer by four bytes.
|
||||
*
|
||||
* @return The 32-bit integer value at the current pointer location.
|
||||
*/
|
||||
int getInt();
|
||||
|
||||
/**
|
||||
* Copy <code>buffer.length</code> bytes from the cell into <code>buffer</code>. The data is copied starting
|
||||
* from the current internal pointer location and afterwards the internal pointer is incremented by <code>buffer.length</code>
|
||||
* bytes.
|
||||
*
|
||||
* @param buffer The array of bytes to copy the cell data into.
|
||||
*/
|
||||
void getByteArray(byte[] buffer);
|
||||
|
||||
/**
|
||||
* Return the number of bytes already packed (for outgoing cells) or unpacked (for incoming cells). This is
|
||||
* equivalent to the internal pointer position.
|
||||
*
|
||||
* @return The number of bytes already consumed from this cell.
|
||||
*/
|
||||
int cellBytesConsumed();
|
||||
|
||||
/**
|
||||
* Return the number of bytes remaining between the current internal pointer and the end of the cell. If fields
|
||||
* are being added to a new cell for transmission then this value indicates the remaining space in bytes for
|
||||
* adding new data. If fields are being read from a received cell then this value describes the number of bytes
|
||||
* which can be read without overflowing the cell.
|
||||
*
|
||||
* @return The number of payload bytes remaining in this cell.
|
||||
*/
|
||||
int cellBytesRemaining();
|
||||
|
||||
/**
|
||||
* Store a byte at the current pointer location and increment the pointer by one byte.
|
||||
*
|
||||
* @param value The byte value to store.
|
||||
*/
|
||||
void putByte(int value);
|
||||
|
||||
/**
|
||||
* Store a byte at the specified offset into the cell.
|
||||
*
|
||||
* @param index The offset in bytes into the cell.
|
||||
* @param value The byte value to store.
|
||||
*/
|
||||
void putByteAt(int index, int value);
|
||||
|
||||
/**
|
||||
* Store a 16-bit short value in big endian order at the current pointer location and
|
||||
* increment the pointer by two bytes.
|
||||
*
|
||||
* @param value The 16-bit short value to store.
|
||||
*/
|
||||
void putShort(int value);
|
||||
|
||||
/**
|
||||
* Store a 16-bit short value in big endian byte order at the specified offset into the cell
|
||||
* and increment the pointer by two bytes.
|
||||
*
|
||||
* @param index The offset in bytes into the cell.
|
||||
* @param value The 16-bit short value to store.
|
||||
*/
|
||||
void putShortAt(int index, int value);
|
||||
|
||||
/**
|
||||
* Store a 32-bit integer value in big endian order at the current pointer location and
|
||||
* increment the pointer by 4 bytes.
|
||||
*
|
||||
* @param value The 32-bit integer value to store.
|
||||
*/
|
||||
void putInt(int value);
|
||||
|
||||
/**
|
||||
* Store the entire array <code>data</code> at the current pointer location and increment
|
||||
* the pointer by <code>data.length</code> bytes.
|
||||
*
|
||||
* @param data The array of bytes to store in the cell.
|
||||
*/
|
||||
void putByteArray(byte[] data);
|
||||
|
||||
/**
|
||||
* Store <code>length</code> bytes of the byte array <code>data</code> starting from
|
||||
* <code>offset</code> into the array at the current pointer location and increment
|
||||
* the pointer by <code>length</code> bytes.
|
||||
*
|
||||
* @param data The source array of bytes.
|
||||
* @param offset The offset into the source array.
|
||||
* @param length The number of bytes from the source array to store.
|
||||
*/
|
||||
void putByteArray(byte[] data, int offset, int length);
|
||||
|
||||
/**
|
||||
* Return the entire cell data as a raw array of bytes. For all cells except
|
||||
* <code>VERSIONS</code>, this array will be exactly <code>CELL_LEN</code> bytes long.
|
||||
*
|
||||
* @return The cell data as an array of bytes.
|
||||
*/
|
||||
byte[] getCellBytes();
|
||||
|
||||
void putString(String string);
|
||||
}
|
95
src/java/com/subgraph/orchid/Circuit.java
Normal file
95
src/java/com/subgraph/orchid/Circuit.java
Normal file
@ -0,0 +1,95 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Circuit represents a logical path through multiple ORs. Circuits are described in
|
||||
* section 5 of tor-spec.txt.
|
||||
*
|
||||
*/
|
||||
public interface Circuit {
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the circuit is presently in the connected state or
|
||||
* <code>false</code> otherwise.
|
||||
*
|
||||
* @return Returns <code>true</code> if the circuit is presently connected, or
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
boolean isPending();
|
||||
|
||||
boolean isClean();
|
||||
|
||||
boolean isMarkedForClose();
|
||||
|
||||
int getSecondsDirty();
|
||||
|
||||
/**
|
||||
* Returns the entry router <code>Connection</code> object of this Circuit. Throws
|
||||
* a TorException if the circuit is not currently open.
|
||||
*
|
||||
* @return The Connection object for the network connection to the entry router of this
|
||||
* circuit.
|
||||
* @throws TorException If this circuit is not currently connected.
|
||||
*/
|
||||
Connection getConnection();
|
||||
|
||||
/**
|
||||
* Returns the curcuit id value for this circuit.
|
||||
*
|
||||
* @return The circuit id value for this circuit.
|
||||
*/
|
||||
int getCircuitId();
|
||||
|
||||
/**
|
||||
* Create a new relay cell which is configured for delivery to the specified
|
||||
* circuit <code>targetNode</code> with command value <code>relayCommand</code>
|
||||
* and a stream id value of <code>streamId</code>. The returned <code>RelayCell</code>
|
||||
* can then be used to populate the payload of the cell before delivering it.
|
||||
*
|
||||
* @param relayCommand The command value to send in the relay cell header.
|
||||
* @param streamId The stream id value to send in the relay cell header.
|
||||
* @param targetNode The target circuit node to encrypt this cell for.
|
||||
* @return A newly created relay cell object.
|
||||
*/
|
||||
RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode);
|
||||
|
||||
/**
|
||||
* Returns the next relay response cell received on this circuit. If no response is
|
||||
* received within <code>CIRCUIT_RELAY_RESPONSE_TIMEOUT</code> milliseconds, <code>null</code>
|
||||
* is returned.
|
||||
*
|
||||
* @return The next relay response cell received on this circuit or <code>null</code> if
|
||||
* a timeout is reached before the next relay cell arrives.
|
||||
*/
|
||||
RelayCell receiveRelayCell();
|
||||
|
||||
/**
|
||||
* Encrypt and deliver the relay cell <code>cell</code>.
|
||||
*
|
||||
* @param cell The relay cell to deliver over this circuit.
|
||||
*/
|
||||
void sendRelayCell(RelayCell cell);
|
||||
|
||||
/**
|
||||
* Return the last node or 'hop' in this circuit.
|
||||
*
|
||||
* @return The final 'hop' or node of this circuit.
|
||||
*/
|
||||
CircuitNode getFinalCircuitNode();
|
||||
|
||||
|
||||
void destroyCircuit();
|
||||
|
||||
void deliverRelayCell(Cell cell);
|
||||
|
||||
void deliverControlCell(Cell cell);
|
||||
|
||||
List<Stream> getActiveStreams();
|
||||
|
||||
void markForClose();
|
||||
|
||||
void appendNode(CircuitNode node);
|
||||
}
|
60
src/java/com/subgraph/orchid/CircuitBuildHandler.java
Normal file
60
src/java/com/subgraph/orchid/CircuitBuildHandler.java
Normal file
@ -0,0 +1,60 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
/**
|
||||
* This callback interface is used for reporting progress when
|
||||
* opening a new circuit. An instance of this interface is passed
|
||||
* to the {@link Circuit#openCircuit(java.util.List, CircuitBuildHandler)}
|
||||
* method.
|
||||
*
|
||||
* The normal sequence of callbacks which are fired when a circuit is opened
|
||||
* successfully is {@link #connectionCompleted(Connection)} for the initial
|
||||
* connection to the entry router, followed by one or more
|
||||
* {@link #nodeAdded(CircuitNode)} as the circuit is extended with new nodes.
|
||||
* When all requested nodes in the path have been added successfully to the
|
||||
* circuit {@link #circuitBuildCompleted(Circuit)} is called and passed the
|
||||
* newly constructed circuit.
|
||||
*
|
||||
* @see Circuit#openCircuit()
|
||||
*
|
||||
*/
|
||||
public interface CircuitBuildHandler {
|
||||
/**
|
||||
* Called when a network connection to the entry node has completed
|
||||
* successfully or if a network connection to the specified entry router
|
||||
* already exists.
|
||||
*
|
||||
* @param connection The completed connection instance.
|
||||
*/
|
||||
void connectionCompleted(Connection connection);
|
||||
|
||||
/**
|
||||
* The circuit build has failed because the network connection to the
|
||||
* entry node failed. No further callback methods will be called after
|
||||
* this failure has been reported.
|
||||
*
|
||||
* @param reason A description of the reason for failing to connect to
|
||||
* the entry node.
|
||||
*/
|
||||
void connectionFailed(String reason);
|
||||
|
||||
/**
|
||||
* A node or 'hop' has been added to the circuit which is being created.
|
||||
*
|
||||
* @param node The newly added circuit node.
|
||||
*/
|
||||
void nodeAdded(CircuitNode node);
|
||||
|
||||
/**
|
||||
* The circuit has been successfully built and is ready for use.
|
||||
*
|
||||
* @param circuit The newly constructed circuit.
|
||||
*/
|
||||
void circuitBuildCompleted(Circuit circuit);
|
||||
|
||||
/**
|
||||
* Called if the circuit build fails after connecting to the entry node.
|
||||
*
|
||||
* @param reason A description of the reason the circuit build has failed.
|
||||
*/
|
||||
void circuitBuildFailed(String reason);
|
||||
}
|
51
src/java/com/subgraph/orchid/CircuitManager.java
Normal file
51
src/java/com/subgraph/orchid/CircuitManager.java
Normal file
@ -0,0 +1,51 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
|
||||
public interface CircuitManager {
|
||||
|
||||
static int DIRECTORY_PURPOSE_CONSENSUS = 1;
|
||||
static int DIRECTORY_PURPOSE_CERTIFICATES = 2;
|
||||
static int DIRECTORY_PURPOSE_DESCRIPTORS = 3;
|
||||
|
||||
/**
|
||||
* Begin automatically building new circuits in the background.
|
||||
*/
|
||||
void startBuildingCircuits();
|
||||
void stopBuildingCircuits(boolean killCircuits);
|
||||
/**
|
||||
* Attempt to open an exit stream to the specified destination <code>hostname</code> and
|
||||
* <code>port</code>.
|
||||
*
|
||||
* @param hostname The name of the host to open an exit connection to.
|
||||
* @param port The port to open an exit connection to.
|
||||
* @return The status response result of attempting to open the exit connection.
|
||||
*/
|
||||
Stream openExitStreamTo(String hostname, int port) throws InterruptedException, TimeoutException, OpenFailedException;
|
||||
|
||||
/**
|
||||
* Attempt to open an exit stream to the destination specified by <code>address</code> and
|
||||
* <code>port</code>.
|
||||
*
|
||||
* @param address The address to open an exit connection to.
|
||||
* @param port The port to open an exit connection to.
|
||||
* @return The status response result of attempting the open the exit connection.
|
||||
*/
|
||||
Stream openExitStreamTo(IPv4Address address, int port) throws InterruptedException, TimeoutException, OpenFailedException;
|
||||
|
||||
|
||||
Stream openDirectoryStream(int purpose) throws InterruptedException, TimeoutException, OpenFailedException;
|
||||
|
||||
Stream openDirectoryStream() throws InterruptedException, TimeoutException, OpenFailedException;
|
||||
|
||||
DirectoryCircuit openDirectoryCircuit() throws OpenFailedException;
|
||||
Circuit getCleanInternalCircuit() throws InterruptedException;
|
||||
|
||||
ExitCircuit openExitCircuitTo(List<Router> path) throws OpenFailedException;
|
||||
InternalCircuit openInternalCircuitTo(List<Router> path) throws OpenFailedException;
|
||||
DirectoryCircuit openDirectoryCircuitTo(List<Router> path) throws OpenFailedException;
|
||||
}
|
90
src/java/com/subgraph/orchid/CircuitNode.java
Normal file
90
src/java/com/subgraph/orchid/CircuitNode.java
Normal file
@ -0,0 +1,90 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents the state of a single onion router hop in a connected or connecting {@link Circuit}
|
||||
*/
|
||||
public interface CircuitNode {
|
||||
/**
|
||||
* Return the {@link Router} associated with this node.
|
||||
*
|
||||
* @return The {@link Router} for this hop of the circuit chain.
|
||||
*/
|
||||
Router getRouter();
|
||||
|
||||
/**
|
||||
* Update the 'forward' cryptographic digest state for this
|
||||
* node with the contents of <code>cell</code>
|
||||
*
|
||||
* @param cell The {@link RelayCell} to add to the digest.
|
||||
*/
|
||||
void updateForwardDigest(RelayCell cell);
|
||||
|
||||
/**
|
||||
* Return the current 'forward' running digest value for this
|
||||
* node as an array of <code>TOR_DIGEST_SIZE</code> bytes.
|
||||
*
|
||||
* @return The current 'forward' running digest value for this node.
|
||||
*/
|
||||
byte[] getForwardDigestBytes();
|
||||
|
||||
/**
|
||||
* Encrypt a {@link RelayCell} for this node with the current
|
||||
* 'forward' cipher state.
|
||||
*
|
||||
* @param cell The {@link RelayCell} to encrypt.
|
||||
*/
|
||||
void encryptForwardCell(RelayCell cell);
|
||||
|
||||
/**
|
||||
* Return the {@link CircuitNode} which immediately preceeds this
|
||||
* one in the circuit node chain or <code>null</code> if this is
|
||||
* the first hop.
|
||||
*
|
||||
* @return The previous {@link CircuitNode} in the chain or <code>
|
||||
* null</code> if this is the first node.
|
||||
*/
|
||||
CircuitNode getPreviousNode();
|
||||
|
||||
/**
|
||||
* Return immediately if the packaging window for this node is open (ie: greater than 0), otherwise
|
||||
* block until the circuit is destroyed or the window is incremented by receiving a RELAY_SENDME cell
|
||||
* from this node.
|
||||
*/
|
||||
void waitForSendWindow();
|
||||
|
||||
/**
|
||||
* If the packaging window for this node is open (ie: greater than 0) this method
|
||||
* decrements the packaging window by 1 and returns immediately, otherwise it will
|
||||
* block until the circuit is destroyed or the window is incremented by receiving
|
||||
* a RELAY_SENDME cell from this node. This method will always decrement the packaging
|
||||
* window before returning unless the circuit has been destroyed.
|
||||
*/
|
||||
void waitForSendWindowAndDecrement();
|
||||
|
||||
/**
|
||||
* This method is called to signal that a RELAY_SENDME cell has been received from this
|
||||
* node and the packaging window should be incremented. This will also wake up any threads
|
||||
* that are waiting for the packaging window to open.
|
||||
*/
|
||||
void incrementSendWindow();
|
||||
|
||||
/**
|
||||
* This method is called when a RELAY_DATA cell is received from this node to decrement
|
||||
* the deliver window counter.
|
||||
*/
|
||||
void decrementDeliverWindow();
|
||||
|
||||
/**
|
||||
* Examines the delivery window and determines if it would be an appropriate time to
|
||||
* send a RELAY_SENDME cell. If this method returns true, it increments the delivery
|
||||
* window assuming that a RELAY_SENDME cell will be transmitted.
|
||||
*
|
||||
* @return Returns true if the deliver window is small enough that sending a RELAY_SENDME
|
||||
* cell would be appropriate.
|
||||
*/
|
||||
boolean considerSendingSendme();
|
||||
|
||||
boolean decryptBackwardCell(Cell cell);
|
||||
}
|
47
src/java/com/subgraph/orchid/Connection.java
Normal file
47
src/java/com/subgraph/orchid/Connection.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
/**
|
||||
* A network connection to a Tor onion router.
|
||||
*/
|
||||
public interface Connection {
|
||||
/**
|
||||
* Return the {@link Router} associated with this connection.
|
||||
*
|
||||
* @return The entry router this connection represents.
|
||||
*/
|
||||
Router getRouter();
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the socket for this connection has been closed. Otherwise, <code>false</code>.
|
||||
*
|
||||
* @return <code>true</code> if this connection is closed or <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isClosed();
|
||||
/**
|
||||
* Send a protocol {@link Cell} on this connection.
|
||||
*
|
||||
* @param cell The {@link Cell} to transfer.
|
||||
* @throws ConnectionIOException If the cell could not be send because the connection is not connected
|
||||
* or if an error occured while sending the cell data.
|
||||
*/
|
||||
void sendCell(Cell cell) throws ConnectionIOException;
|
||||
|
||||
/**
|
||||
* Remove a Circuit which has been bound to this Connection by a previous call to {@link #bindCircuit(Circuit) bindCircuit}.
|
||||
* After removing a Circuit, any further received incoming cells for the Circuit will be discarded.
|
||||
*
|
||||
* @param circuit The Circuit to remove.
|
||||
*/
|
||||
void removeCircuit(Circuit circuit);
|
||||
|
||||
/**
|
||||
* Choose an available circuit id value and bind this Circuit to that id value, returning the id value.
|
||||
* Once bound, any incoming relay cells will be delivered to the Circuit with {@link Circuit#deliverRelayCell(Cell)}
|
||||
* and other cells will be delivered with {@link Circuit#deliverControlCell(Cell)}.
|
||||
*
|
||||
* @param circuit The Circuit to bind to this connection.
|
||||
* @return the circuit id value for this binding.
|
||||
*/
|
||||
int bindCircuit(Circuit circuit);
|
||||
}
|
21
src/java/com/subgraph/orchid/ConnectionCache.java
Normal file
21
src/java/com/subgraph/orchid/ConnectionCache.java
Normal file
@ -0,0 +1,21 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
public interface ConnectionCache {
|
||||
/**
|
||||
* Returns a completed connection to the specified router. If an open connection
|
||||
* to the requested router already exists it is returned, otherwise a new connection
|
||||
* is opened.
|
||||
*
|
||||
* @param router The router to which a connection is requested.
|
||||
* @param isDirectoryConnection Is this going to be used as a directory connection.
|
||||
* @return a completed connection to the specified router.
|
||||
* @throws InterruptedException if thread is interrupted while waiting for connection to complete.
|
||||
* @throws ConnectionTimeoutException if timeout expires before connection completes.
|
||||
* @throws ConnectionFailedException if connection fails due to I/O error
|
||||
* @throws ConnectionHandshakeException if connection fails because an error occurred during handshake phase
|
||||
*/
|
||||
Connection getConnectionTo(Router router, boolean isDirectoryConnection) throws InterruptedException, ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException;
|
||||
|
||||
void close();
|
||||
}
|
11
src/java/com/subgraph/orchid/ConnectionFailedException.java
Normal file
11
src/java/com/subgraph/orchid/ConnectionFailedException.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public class ConnectionFailedException extends ConnectionIOException {
|
||||
|
||||
private static final long serialVersionUID = -4484347156587613574L;
|
||||
|
||||
public ConnectionFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public class ConnectionHandshakeException extends ConnectionIOException {
|
||||
|
||||
private static final long serialVersionUID = -2544633445932967966L;
|
||||
|
||||
public ConnectionHandshakeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
14
src/java/com/subgraph/orchid/ConnectionIOException.java
Normal file
14
src/java/com/subgraph/orchid/ConnectionIOException.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public class ConnectionIOException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5537650738995969203L;
|
||||
|
||||
public ConnectionIOException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ConnectionIOException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
14
src/java/com/subgraph/orchid/ConnectionTimeoutException.java
Normal file
14
src/java/com/subgraph/orchid/ConnectionTimeoutException.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public class ConnectionTimeoutException extends ConnectionIOException {
|
||||
|
||||
private static final long serialVersionUID = -6098661610150140151L;
|
||||
|
||||
public ConnectionTimeoutException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ConnectionTimeoutException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
44
src/java/com/subgraph/orchid/ConsensusDocument.java
Normal file
44
src/java/com/subgraph/orchid/ConsensusDocument.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.Timestamp;
|
||||
|
||||
public interface ConsensusDocument extends Document {
|
||||
enum ConsensusFlavor { NS, MICRODESC };
|
||||
enum SignatureStatus { STATUS_VERIFIED, STATUS_FAILED, STATUS_NEED_CERTS };
|
||||
|
||||
interface RequiredCertificate {
|
||||
int getDownloadFailureCount();
|
||||
void incrementDownloadFailureCount();
|
||||
HexDigest getAuthorityIdentity();
|
||||
HexDigest getSigningKey();
|
||||
}
|
||||
|
||||
ConsensusFlavor getFlavor();
|
||||
Timestamp getValidAfterTime();
|
||||
Timestamp getFreshUntilTime();
|
||||
Timestamp getValidUntilTime();
|
||||
int getConsensusMethod();
|
||||
int getVoteSeconds();
|
||||
int getDistSeconds();
|
||||
Set<String> getClientVersions();
|
||||
Set<String> getServerVersions();
|
||||
boolean isLive();
|
||||
List<RouterStatus> getRouterStatusEntries();
|
||||
|
||||
SignatureStatus verifySignatures();
|
||||
Set<RequiredCertificate> getRequiredCertificates();
|
||||
|
||||
HexDigest getSigningHash();
|
||||
HexDigest getSigningHash256();
|
||||
|
||||
int getCircWindowParameter();
|
||||
int getWeightScaleParameter();
|
||||
|
||||
int getBandwidthWeight(String tag);
|
||||
|
||||
boolean getUseNTorHandshake();
|
||||
}
|
65
src/java/com/subgraph/orchid/Descriptor.java
Normal file
65
src/java/com/subgraph/orchid/Descriptor.java
Normal file
@ -0,0 +1,65 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
public interface Descriptor extends Document {
|
||||
enum CacheLocation { NOT_CACHED, CACHED_CACHEFILE, CACHED_JOURNAL }
|
||||
|
||||
HexDigest getDescriptorDigest();
|
||||
void setLastListed(long timestamp);
|
||||
long getLastListed();
|
||||
void setCacheLocation(CacheLocation location);
|
||||
CacheLocation getCacheLocation();
|
||||
int getBodyLength();
|
||||
|
||||
/**
|
||||
* Return the public key used to encrypt EXTEND cells while establishing
|
||||
* a circuit through this router.
|
||||
*
|
||||
* @return The onion routing protocol key for this router.
|
||||
*/
|
||||
TorPublicKey getOnionKey();
|
||||
byte[] getNTorOnionKey();
|
||||
|
||||
/**
|
||||
* Return the IPv4 address of this router.
|
||||
*
|
||||
* @return The IPv4 address of this router.
|
||||
*/
|
||||
IPv4Address getAddress();
|
||||
|
||||
/**
|
||||
* Return the port on which this node accepts TLS connections
|
||||
* for the main OR protocol, or 0 if no router service is advertised.
|
||||
*
|
||||
* @return The onion routing port, or 0 if not a router.
|
||||
*/
|
||||
int getRouterPort();
|
||||
Set<String> getFamilyMembers();
|
||||
|
||||
/**
|
||||
* Return true if the exit policy of this router permits connections
|
||||
* to the specified destination endpoint.
|
||||
*
|
||||
* @param address The IPv4 address of the destination.
|
||||
* @param port The destination port.
|
||||
*
|
||||
* @return True if an exit connection to the specified destination is allowed
|
||||
* or false otherwise.
|
||||
*/
|
||||
boolean exitPolicyAccepts(IPv4Address address, int port);
|
||||
|
||||
/**
|
||||
* Return true if the exit policy of this router accepts most connections
|
||||
* to the specified destination port.
|
||||
*
|
||||
* @param port The destination port.
|
||||
* @return True if an exit connection to the specified destination port is generally allowed
|
||||
* or false otherwise.
|
||||
*/
|
||||
boolean exitPolicyAccepts(int port);
|
||||
}
|
48
src/java/com/subgraph/orchid/Directory.java
Normal file
48
src/java/com/subgraph/orchid/Directory.java
Normal file
@ -0,0 +1,48 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.subgraph.orchid.ConsensusDocument.RequiredCertificate;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.events.EventHandler;
|
||||
|
||||
/**
|
||||
*
|
||||
* Main interface for accessing directory information and interacting
|
||||
* with directory authorities and caches.
|
||||
*
|
||||
*/
|
||||
public interface Directory {
|
||||
boolean haveMinimumRouterInfo();
|
||||
void loadFromStore();
|
||||
void close();
|
||||
void waitUntilLoaded();
|
||||
void storeCertificates();
|
||||
|
||||
Collection<DirectoryServer> getDirectoryAuthorities();
|
||||
DirectoryServer getRandomDirectoryAuthority();
|
||||
void addCertificate(KeyCertificate certificate);
|
||||
Set<RequiredCertificate> getRequiredCertificates();
|
||||
void addRouterMicrodescriptors(List<RouterMicrodescriptor> microdescriptors);
|
||||
void addRouterDescriptors(List<RouterDescriptor> descriptors);
|
||||
void addConsensusDocument(ConsensusDocument consensus, boolean fromCache);
|
||||
ConsensusDocument getCurrentConsensusDocument();
|
||||
boolean hasPendingConsensus();
|
||||
void registerConsensusChangedHandler(EventHandler handler);
|
||||
void unregisterConsensusChangedHandler(EventHandler handler);
|
||||
Router getRouterByName(String name);
|
||||
Router getRouterByIdentity(HexDigest identity);
|
||||
List<Router> getRouterListByNames(List<String> names);
|
||||
List<Router> getRoutersWithDownloadableDescriptors();
|
||||
List<Router> getAllRouters();
|
||||
|
||||
RouterMicrodescriptor getMicrodescriptorFromCache(HexDigest descriptorDigest);
|
||||
RouterDescriptor getBasicDescriptorFromCache(HexDigest descriptorDigest);
|
||||
|
||||
GuardEntry createGuardEntryFor(Router router);
|
||||
List<GuardEntry> getGuardEntries();
|
||||
void removeGuardEntry(GuardEntry entry);
|
||||
void addGuardEntry(GuardEntry entry);
|
||||
}
|
16
src/java/com/subgraph/orchid/DirectoryCircuit.java
Normal file
16
src/java/com/subgraph/orchid/DirectoryCircuit.java
Normal file
@ -0,0 +1,16 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public interface DirectoryCircuit extends Circuit {
|
||||
/**
|
||||
* Open an anonymous connection to the directory service running on the
|
||||
* final node in this circuit.
|
||||
*
|
||||
* @param timeout in milliseconds
|
||||
* @param autoclose if set to true, closing stream also marks this circuit for close
|
||||
*
|
||||
* @return The status response returned by trying to open the stream.
|
||||
*/
|
||||
Stream openDirectoryStream(long timeout, boolean autoclose) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
||||
}
|
27
src/java/com/subgraph/orchid/DirectoryDownloader.java
Normal file
27
src/java/com/subgraph/orchid/DirectoryDownloader.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.subgraph.orchid.ConsensusDocument.RequiredCertificate;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryRequestFailedException;
|
||||
|
||||
public interface DirectoryDownloader {
|
||||
void start(Directory directory);
|
||||
void stop();
|
||||
|
||||
RouterDescriptor downloadBridgeDescriptor(Router bridge) throws DirectoryRequestFailedException;
|
||||
|
||||
ConsensusDocument downloadCurrentConsensus(boolean useMicrodescriptors) throws DirectoryRequestFailedException;
|
||||
ConsensusDocument downloadCurrentConsensus(boolean useMicrodescriptors, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
||||
|
||||
List<KeyCertificate> downloadKeyCertificates(Set<RequiredCertificate> required) throws DirectoryRequestFailedException;
|
||||
List<KeyCertificate> downloadKeyCertificates(Set<RequiredCertificate> required, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
||||
|
||||
List<RouterDescriptor> downloadRouterDescriptors(Set<HexDigest> fingerprints) throws DirectoryRequestFailedException;
|
||||
List<RouterDescriptor> downloadRouterDescriptors(Set<HexDigest> fingerprints, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
||||
|
||||
List<RouterMicrodescriptor> downloadRouterMicrodescriptors(Set<HexDigest> fingerprints) throws DirectoryRequestFailedException;
|
||||
List<RouterMicrodescriptor> downloadRouterMicrodescriptors(Set<HexDigest> fingerprints, DirectoryCircuit circuit) throws DirectoryRequestFailedException;
|
||||
}
|
22
src/java/com/subgraph/orchid/DirectoryServer.java
Normal file
22
src/java/com/subgraph/orchid/DirectoryServer.java
Normal file
@ -0,0 +1,22 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
|
||||
/**
|
||||
* Represents a directory authority server or a directory cache.
|
||||
*/
|
||||
public interface DirectoryServer extends Router {
|
||||
int getDirectoryPort();
|
||||
boolean isV2Authority();
|
||||
boolean isV3Authority();
|
||||
HexDigest getV3Identity();
|
||||
boolean isHiddenServiceAuthority();
|
||||
boolean isBridgeAuthority();
|
||||
boolean isExtraInfoCache();
|
||||
|
||||
KeyCertificate getCertificateByFingerprint(HexDigest fingerprint);
|
||||
List<KeyCertificate> getCertificates();
|
||||
void addCertificate(KeyCertificate certificate);
|
||||
}
|
36
src/java/com/subgraph/orchid/DirectoryStore.java
Normal file
36
src/java/com/subgraph/orchid/DirectoryStore.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public interface DirectoryStore {
|
||||
enum CacheFile {
|
||||
CERTIFICATES("certificates"),
|
||||
CONSENSUS("consensus"),
|
||||
CONSENSUS_MICRODESC("consensus-microdesc"),
|
||||
MICRODESCRIPTOR_CACHE("cached-microdescs"),
|
||||
MICRODESCRIPTOR_JOURNAL("cached-microdescs.new"),
|
||||
DESCRIPTOR_CACHE("cached-descriptors"),
|
||||
DESCRIPTOR_JOURNAL("cached-descriptors.new"),
|
||||
STATE("state");
|
||||
|
||||
final private String filename;
|
||||
|
||||
CacheFile(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer loadCacheFile(CacheFile cacheFile);
|
||||
void writeData(CacheFile cacheFile, ByteBuffer data);
|
||||
void writeDocument(CacheFile cacheFile, Document document);
|
||||
void writeDocumentList(CacheFile cacheFile, List<? extends Document> documents);
|
||||
void appendDocumentList(CacheFile cacheFile, List<? extends Document> documents);
|
||||
|
||||
void removeCacheFile(CacheFile cacheFile);
|
||||
void removeAllCacheFiles();
|
||||
}
|
9
src/java/com/subgraph/orchid/Document.java
Normal file
9
src/java/com/subgraph/orchid/Document.java
Normal file
@ -0,0 +1,9 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface Document {
|
||||
ByteBuffer getRawDocumentBytes();
|
||||
String getRawDocumentData();
|
||||
boolean isValidDocument();
|
||||
}
|
50
src/java/com/subgraph/orchid/ExitCircuit.java
Normal file
50
src/java/com/subgraph/orchid/ExitCircuit.java
Normal file
@ -0,0 +1,50 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
||||
|
||||
public interface ExitCircuit extends Circuit {
|
||||
|
||||
/**
|
||||
* Open an exit stream from the final node in this circuit to the
|
||||
* specified target address and port.
|
||||
*
|
||||
* @param address The network address of the exit target.
|
||||
* @param port The port of the exit target.
|
||||
* @return The status response returned by trying to open the stream.
|
||||
*/
|
||||
Stream openExitStream(IPv4Address address, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
||||
|
||||
/**
|
||||
* Open an exit stream from the final node in this circuit to the
|
||||
* specified target hostname and port.
|
||||
*
|
||||
* @param hostname The network hostname of the exit target.
|
||||
* @param port The port of the exit target.
|
||||
* @return The status response returned by trying to open the stream.
|
||||
*/
|
||||
Stream openExitStream(String hostname, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
||||
|
||||
/**
|
||||
* Return true if the final node of this circuit is believed to be able to connect to
|
||||
* the specified <code>ExitTarget</code>. Returns false if the target destination is
|
||||
* not permitted by the exit policy of the final node in this circuit or if the target
|
||||
* has been previously recorded to have failed through this circuit.
|
||||
*
|
||||
* @param target The exit destination.
|
||||
* @return Return true if is likely that the final node of this circuit can connect to the specified exit target.
|
||||
*/
|
||||
boolean canHandleExitTo(ExitTarget target);
|
||||
|
||||
boolean canHandleExitToPort(int port);
|
||||
/**
|
||||
* Records the specified <code>ExitTarget</code> as a failed connection so that {@link #canHandleExitTo(ExitTarget)} will
|
||||
* no longer return true for this exit destination.
|
||||
*
|
||||
* @param target The <code>ExitTarget</code> to which a connection has failed through this circuit.
|
||||
*/
|
||||
public void recordFailedExitTarget(ExitTarget target);
|
||||
|
||||
}
|
18
src/java/com/subgraph/orchid/GuardEntry.java
Normal file
18
src/java/com/subgraph/orchid/GuardEntry.java
Normal file
@ -0,0 +1,18 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface GuardEntry {
|
||||
boolean isAdded();
|
||||
void markAsDown();
|
||||
void clearDownSince();
|
||||
String getNickname();
|
||||
String getIdentity();
|
||||
String getVersion();
|
||||
Date getCreatedTime();
|
||||
Date getDownSince();
|
||||
Date getLastConnectAttempt();
|
||||
Date getUnlistedSince();
|
||||
boolean testCurrentlyUsable();
|
||||
Router getRouterForEntry();
|
||||
}
|
8
src/java/com/subgraph/orchid/HiddenServiceCircuit.java
Normal file
8
src/java/com/subgraph/orchid/HiddenServiceCircuit.java
Normal file
@ -0,0 +1,8 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
|
||||
public interface HiddenServiceCircuit extends Circuit {
|
||||
Stream openStream(int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException;
|
||||
}
|
7
src/java/com/subgraph/orchid/InternalCircuit.java
Normal file
7
src/java/com/subgraph/orchid/InternalCircuit.java
Normal file
@ -0,0 +1,7 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public interface InternalCircuit extends Circuit {
|
||||
DirectoryCircuit cannibalizeToDirectory(Router target);
|
||||
Circuit cannibalizeToIntroductionPoint(Router target);
|
||||
HiddenServiceCircuit connectHiddenService(CircuitNode node);
|
||||
}
|
78
src/java/com/subgraph/orchid/KeyCertificate.java
Normal file
78
src/java/com/subgraph/orchid/KeyCertificate.java
Normal file
@ -0,0 +1,78 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.data.Timestamp;
|
||||
|
||||
/**
|
||||
* This class represents a key certificate document as specified in
|
||||
* dir-spec.txt (section 3.1). These documents are published by
|
||||
* directory authorities and bind a long-term identity key to a
|
||||
* more temporary signing key.
|
||||
*/
|
||||
public interface KeyCertificate extends Document {
|
||||
/**
|
||||
* Return the network address of this directory authority
|
||||
* or <code>null</code> if no address was specified in the certificate.
|
||||
*
|
||||
* @return The network address of the directory authority this certificate
|
||||
* belongs to, or <code>null</code> if not available.
|
||||
*/
|
||||
IPv4Address getDirectoryAddress();
|
||||
|
||||
/**
|
||||
* Return the port on which this directory authority answers
|
||||
* directory requests or 0 if no port was specified in the certificate.
|
||||
*
|
||||
* @return The port of this directory authority listens on or 0 if
|
||||
* no port was specified in the certificate.
|
||||
*/
|
||||
int getDirectoryPort();
|
||||
|
||||
/**
|
||||
* Return fingerprint of the authority identity key as specified in
|
||||
* the certificate.
|
||||
*
|
||||
* @return The authority identity key fingerprint.
|
||||
*/
|
||||
HexDigest getAuthorityFingerprint();
|
||||
|
||||
/**
|
||||
* Return the authority identity public key from the certificate.
|
||||
*
|
||||
* @return The authority identity public key.
|
||||
*/
|
||||
TorPublicKey getAuthorityIdentityKey();
|
||||
|
||||
/**
|
||||
* Return the authority signing public key from the certificate.
|
||||
*
|
||||
* @return The authority signing public key.
|
||||
*/
|
||||
TorPublicKey getAuthoritySigningKey();
|
||||
|
||||
/**
|
||||
* Return the time when this document and corresponding keys were
|
||||
* generated.
|
||||
*
|
||||
* @return The time this document was generated and published.
|
||||
*/
|
||||
Timestamp getKeyPublishedTime();
|
||||
|
||||
/**
|
||||
* Return the time after which this document and signing key are
|
||||
* no longer valid.
|
||||
*
|
||||
* @return The expiry time of this document and signing key.
|
||||
*/
|
||||
Timestamp getKeyExpiryTime();
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the current time is past the key
|
||||
* expiry time of this certificate.
|
||||
*
|
||||
* @return True if this certificate is currently expired.
|
||||
*/
|
||||
boolean isExpired();
|
||||
}
|
13
src/java/com/subgraph/orchid/OpenFailedException.java
Normal file
13
src/java/com/subgraph/orchid/OpenFailedException.java
Normal file
@ -0,0 +1,13 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public class OpenFailedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1989001056577214666L;
|
||||
|
||||
public OpenFailedException() {
|
||||
}
|
||||
|
||||
public OpenFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
65
src/java/com/subgraph/orchid/RelayCell.java
Normal file
65
src/java/com/subgraph/orchid/RelayCell.java
Normal file
@ -0,0 +1,65 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
|
||||
public interface RelayCell extends Cell {
|
||||
|
||||
final static int LENGTH_OFFSET = 12;
|
||||
final static int RECOGNIZED_OFFSET = 4;
|
||||
final static int DIGEST_OFFSET = 8;
|
||||
final static int HEADER_SIZE = 14;
|
||||
|
||||
final static int RELAY_BEGIN = 1;
|
||||
final static int RELAY_DATA = 2;
|
||||
final static int RELAY_END = 3;
|
||||
final static int RELAY_CONNECTED = 4;
|
||||
final static int RELAY_SENDME = 5;
|
||||
final static int RELAY_EXTEND = 6;
|
||||
final static int RELAY_EXTENDED = 7;
|
||||
final static int RELAY_TRUNCATE = 8;
|
||||
final static int RELAY_TRUNCATED = 9;
|
||||
final static int RELAY_DROP = 10;
|
||||
final static int RELAY_RESOLVE = 11;
|
||||
final static int RELAY_RESOLVED = 12;
|
||||
final static int RELAY_BEGIN_DIR = 13;
|
||||
final static int RELAY_EXTEND2 = 14;
|
||||
final static int RELAY_EXTENDED2 = 15;
|
||||
|
||||
final static int RELAY_COMMAND_ESTABLISH_INTRO = 32;
|
||||
final static int RELAY_COMMAND_ESTABLISH_RENDEZVOUS = 33;
|
||||
final static int RELAY_COMMAND_INTRODUCE1 = 34;
|
||||
final static int RELAY_COMMAND_INTRODUCE2 = 35;
|
||||
final static int RELAY_COMMAND_RENDEZVOUS1 = 36;
|
||||
final static int RELAY_COMMAND_RENDEZVOUS2 = 37;
|
||||
final static int RELAY_COMMAND_INTRO_ESTABLISHED = 38;
|
||||
final static int RELAY_COMMAND_RENDEZVOUS_ESTABLISHED = 39;
|
||||
final static int RELAY_COMMAND_INTRODUCE_ACK = 40;
|
||||
|
||||
final static int REASON_MISC = 1;
|
||||
final static int REASON_RESOLVEFAILED = 2;
|
||||
final static int REASON_CONNECTREFUSED = 3;
|
||||
final static int REASON_EXITPOLICY = 4;
|
||||
final static int REASON_DESTROY = 5;
|
||||
final static int REASON_DONE = 6;
|
||||
final static int REASON_TIMEOUT = 7;
|
||||
final static int REASON_NOROUTE = 8;
|
||||
final static int REASON_HIBERNATING = 9;
|
||||
final static int REASON_INTERNAL = 10;
|
||||
final static int REASON_RESOURCELIMIT = 11;
|
||||
final static int REASON_CONNRESET = 12;
|
||||
final static int REASON_TORPROTOCOL = 13;
|
||||
final static int REASON_NOTDIRECTORY = 14;
|
||||
|
||||
int getStreamId();
|
||||
int getRelayCommand();
|
||||
/**
|
||||
* Return the circuit node this cell was received from for outgoing cells or the destination circuit node
|
||||
* for outgoing cells.
|
||||
*/
|
||||
CircuitNode getCircuitNode();
|
||||
ByteBuffer getPayloadBuffer();
|
||||
void setLength();
|
||||
void setDigest(byte[] digest);
|
||||
}
|
35
src/java/com/subgraph/orchid/Revision.java
Normal file
35
src/java/com/subgraph/orchid/Revision.java
Normal file
@ -0,0 +1,35 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class Revision {
|
||||
private final static String REVISION_FILE_PATH = "/build-revision";
|
||||
|
||||
public static String getBuildRevision() {
|
||||
final InputStream input = tryResourceOpen();
|
||||
if(input == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
return readFirstLine(input);
|
||||
} catch (IOException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream tryResourceOpen() {
|
||||
return Revision.class.getResourceAsStream(REVISION_FILE_PATH);
|
||||
}
|
||||
|
||||
private static String readFirstLine(InputStream input) throws IOException {
|
||||
try {
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
|
||||
return reader.readLine();
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
47
src/java/com/subgraph/orchid/Router.java
Normal file
47
src/java/com/subgraph/orchid/Router.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
public interface Router {
|
||||
|
||||
String getNickname();
|
||||
String getCountryCode();
|
||||
IPv4Address getAddress();
|
||||
int getOnionPort();
|
||||
int getDirectoryPort();
|
||||
TorPublicKey getIdentityKey();
|
||||
HexDigest getIdentityHash();
|
||||
boolean isDescriptorDownloadable();
|
||||
|
||||
String getVersion();
|
||||
Descriptor getCurrentDescriptor();
|
||||
HexDigest getDescriptorDigest();
|
||||
HexDigest getMicrodescriptorDigest();
|
||||
|
||||
TorPublicKey getOnionKey();
|
||||
byte[] getNTorOnionKey();
|
||||
|
||||
boolean hasBandwidth();
|
||||
int getEstimatedBandwidth();
|
||||
int getMeasuredBandwidth();
|
||||
|
||||
Set<String> getFamilyMembers();
|
||||
int getAverageBandwidth();
|
||||
int getBurstBandwidth();
|
||||
int getObservedBandwidth();
|
||||
boolean isHibernating();
|
||||
boolean isRunning();
|
||||
boolean isValid();
|
||||
boolean isBadExit();
|
||||
boolean isPossibleGuard();
|
||||
boolean isExit();
|
||||
boolean isFast();
|
||||
boolean isStable();
|
||||
boolean isHSDirectory();
|
||||
boolean exitPolicyAccepts(IPv4Address address, int port);
|
||||
boolean exitPolicyAccepts(int port);
|
||||
}
|
164
src/java/com/subgraph/orchid/RouterDescriptor.java
Normal file
164
src/java/com/subgraph/orchid/RouterDescriptor.java
Normal file
@ -0,0 +1,164 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.Timestamp;
|
||||
import com.subgraph.orchid.data.exitpolicy.ExitPolicy;
|
||||
|
||||
/**
|
||||
* Directory information about a single onion router. This interface
|
||||
* provides access to the fields of a router descriptor document which
|
||||
* has been published through to Tor directory system.
|
||||
*/
|
||||
public interface RouterDescriptor extends Descriptor {
|
||||
/**
|
||||
* Returns the nickname of this router.
|
||||
*
|
||||
* @return The nickname of this router.
|
||||
*/
|
||||
String getNickname();
|
||||
|
||||
|
||||
/**
|
||||
* Return the port on which this router provides directory related
|
||||
* HTTP connections, or 0 if this node does not provide directory
|
||||
* services.
|
||||
*
|
||||
* @return The directory service port, or 0 if not a directory server.
|
||||
*/
|
||||
int getDirectoryPort();
|
||||
|
||||
/**
|
||||
* Returns the volume of traffic in bytes per second that this router
|
||||
* is willing to sustain over long periods.
|
||||
*
|
||||
* @return The average bandwidth of this router in bytes per second.
|
||||
*/
|
||||
int getAverageBandwidth();
|
||||
|
||||
/**
|
||||
* Returns the volume of traffic in bytes per second that this router
|
||||
* is willing to sustain in very short intervals.
|
||||
*
|
||||
* @return The burst bandwidth of this router in bytes per second.
|
||||
*/
|
||||
int getBurstBandwidth();
|
||||
|
||||
/**
|
||||
* Returns the volume of traffic in bytes per second that this router
|
||||
* is estimated to be able to sustain.
|
||||
*
|
||||
* @return The observed bandwidth capacity of this router in bytes per second.
|
||||
*/
|
||||
int getObservedBandwidth();
|
||||
|
||||
/**
|
||||
* Return a human-readable string describing the system on which this router
|
||||
* is running, including possibly the operating system version and Tor
|
||||
* implementation version.
|
||||
*
|
||||
* @return A string describing the platform this router is running on.
|
||||
*/
|
||||
String getPlatform();
|
||||
|
||||
/**
|
||||
* Return the time this descriptor was generated.
|
||||
*
|
||||
* @return The time this descriptor was generated.
|
||||
*/
|
||||
Timestamp getPublishedTime();
|
||||
|
||||
/**
|
||||
* Return a fingerprint of the public key of this router. The fingerprint
|
||||
* is an optional field, so this method may return null if the descriptor
|
||||
* of the router did not include the 'fingerprint' field.
|
||||
*
|
||||
* @return The fingerprint of this router, or null if no fingerprint is available.
|
||||
*/
|
||||
HexDigest getFingerprint();
|
||||
|
||||
/**
|
||||
* Return the number of seconds this router has been running.
|
||||
*
|
||||
* @return The number of seconds this router has been running.
|
||||
*/
|
||||
int getUptime();
|
||||
|
||||
/**
|
||||
* Return the long-term identity and signing public key for this
|
||||
* router.
|
||||
*
|
||||
* @return The long-term identity and signing public key for this router.
|
||||
*/
|
||||
TorPublicKey getIdentityKey();
|
||||
|
||||
/**
|
||||
* Return a string which describes how to contact the server's administrator.
|
||||
* This is an optional field, so this method will return null if the descriptor
|
||||
* of this router did not include the 'contact' field.
|
||||
*
|
||||
* @return The contact information for this router, or null if not available.
|
||||
*/
|
||||
String getContact();
|
||||
|
||||
/**
|
||||
* Return true if this router is currently hibernating and not suitable for
|
||||
* building new circuits.
|
||||
*
|
||||
* @return True if this router is currently hibernating.
|
||||
*/
|
||||
boolean isHibernating();
|
||||
|
||||
/**
|
||||
* Returns true if this router stores and serves hidden service descriptors.
|
||||
*
|
||||
* @return True if this router is a hidden service directory.
|
||||
*/
|
||||
boolean isHiddenServiceDirectory();
|
||||
|
||||
/**
|
||||
* Return true if this router is running a version of Tor which supports the
|
||||
* newer enhanced DNS logic. If false, this router should be used for reverse
|
||||
* hostname lookups.
|
||||
*
|
||||
* @return True if this router supports newer enhanced DNS logic.
|
||||
*/
|
||||
boolean supportsEventDNS();
|
||||
|
||||
/**
|
||||
* Returns true if this router is a directory cache that provides extra-info
|
||||
* documents.
|
||||
*
|
||||
* @return True if this router provides an extra-info document directory service.
|
||||
*/
|
||||
boolean cachesExtraInfo();
|
||||
|
||||
/**
|
||||
* Return a digest of this router's extra-info document, or null if not
|
||||
* available. This is an optional field and will only be present if the
|
||||
* 'extra-info-digest' field was present in the original router descriptor.
|
||||
*
|
||||
* @return The digest of the router extra-info-document, or null if not available.
|
||||
*/
|
||||
HexDigest getExtraInfoDigest();
|
||||
|
||||
/**
|
||||
* Return true if this router allows single-hop circuits to make exit connections.
|
||||
*
|
||||
* @return True if this router allows single-hop circuits to make exit connections.
|
||||
*/
|
||||
boolean allowsSingleHopExits();
|
||||
|
||||
/**
|
||||
* Compare two router descriptors and return true if this router descriptor was published
|
||||
* at a later time than the <code>other</code> descriptor.
|
||||
*
|
||||
* @param other Another router descriptor to compare.
|
||||
* @return True if this descriptor was published later than <code>other</code>
|
||||
*/
|
||||
boolean isNewerThan(RouterDescriptor other);
|
||||
|
||||
ExitPolicy getExitPolicy();
|
||||
|
||||
|
||||
}
|
6
src/java/com/subgraph/orchid/RouterMicrodescriptor.java
Normal file
6
src/java/com/subgraph/orchid/RouterMicrodescriptor.java
Normal file
@ -0,0 +1,6 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
public interface RouterMicrodescriptor extends Descriptor {
|
||||
|
||||
}
|
24
src/java/com/subgraph/orchid/RouterStatus.java
Normal file
24
src/java/com/subgraph/orchid/RouterStatus.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.data.Timestamp;
|
||||
import com.subgraph.orchid.data.exitpolicy.ExitPorts;
|
||||
|
||||
public interface RouterStatus {
|
||||
String getNickname();
|
||||
HexDigest getIdentity();
|
||||
HexDigest getDescriptorDigest();
|
||||
HexDigest getMicrodescriptorDigest();
|
||||
Timestamp getPublicationTime();
|
||||
IPv4Address getAddress();
|
||||
int getRouterPort();
|
||||
boolean isDirectory();
|
||||
int getDirectoryPort();
|
||||
boolean hasFlag(String flag);
|
||||
String getVersion();
|
||||
boolean hasBandwidth();
|
||||
int getEstimatedBandwidth();
|
||||
int getMeasuredBandwidth();
|
||||
ExitPorts getExitPorts();
|
||||
}
|
6
src/java/com/subgraph/orchid/SocksPortListener.java
Normal file
6
src/java/com/subgraph/orchid/SocksPortListener.java
Normal file
@ -0,0 +1,6 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public interface SocksPortListener {
|
||||
void addListeningPort(int port);
|
||||
void stop();
|
||||
}
|
49
src/java/com/subgraph/orchid/Stream.java
Normal file
49
src/java/com/subgraph/orchid/Stream.java
Normal file
@ -0,0 +1,49 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface Stream {
|
||||
/**
|
||||
* Returns the {@link Circuit} this stream belongs to.
|
||||
*
|
||||
* @return The {@link Circuit} this stream belongs to.
|
||||
*/
|
||||
Circuit getCircuit();
|
||||
|
||||
/**
|
||||
* Returns the stream id value of this stream.
|
||||
*
|
||||
* @return The stream id value of this stream.
|
||||
*/
|
||||
int getStreamId();
|
||||
|
||||
|
||||
CircuitNode getTargetNode();
|
||||
|
||||
/**
|
||||
* Close this stream.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} for sending data on this stream.
|
||||
*
|
||||
* @return An {@link InputStream} for transferring data on this stream.
|
||||
*/
|
||||
InputStream getInputStream();
|
||||
|
||||
/**
|
||||
* Returns an {@link OutputStream} for receiving data from this stream.
|
||||
*
|
||||
* @return An {@link OutputStream} for receiving data from this stream.
|
||||
*/
|
||||
OutputStream getOutputStream();
|
||||
|
||||
/**
|
||||
* If the circuit and stream level packaging windows are open for this stream
|
||||
* this method returns immediately, otherwise it blocks until both windows are
|
||||
* open or the stream is closed.
|
||||
*/
|
||||
void waitForSendWindow();
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
public class StreamConnectFailedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 8103571310659595097L;
|
||||
private final int reason;
|
||||
|
||||
public StreamConnectFailedException(int reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public int getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public boolean isReasonRetryable() {
|
||||
return isRetryableReason(reason);
|
||||
}
|
||||
|
||||
/* Copied from edge_reason_is_retriable() since this is not specified */
|
||||
private static boolean isRetryableReason(int reasonCode) {
|
||||
switch(reasonCode) {
|
||||
case RelayCell.REASON_HIBERNATING:
|
||||
case RelayCell.REASON_RESOURCELIMIT:
|
||||
case RelayCell.REASON_RESOLVEFAILED:
|
||||
case RelayCell.REASON_EXITPOLICY:
|
||||
case RelayCell.REASON_MISC:
|
||||
case RelayCell.REASON_NOROUTE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
167
src/java/com/subgraph/orchid/Tor.java
Normal file
167
src/java/com/subgraph/orchid/Tor.java
Normal file
@ -0,0 +1,167 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.charset.Charset;
|
||||
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.connections.ConnectionCacheImpl;
|
||||
import com.subgraph.orchid.directory.DirectoryImpl;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
||||
import com.subgraph.orchid.socks.SocksPortListenerImpl;
|
||||
|
||||
/**
|
||||
* The <code>Tor</code> class is a collection of static methods for instantiating
|
||||
* various subsystem modules.
|
||||
*/
|
||||
public class Tor {
|
||||
private final static Logger logger = Logger.getLogger(Tor.class.getName());
|
||||
|
||||
public final static int BOOTSTRAP_STATUS_STARTING = 0;
|
||||
public final static int BOOTSTRAP_STATUS_CONN_DIR = 5;
|
||||
public final static int BOOTSTRAP_STATUS_HANDSHAKE_DIR = 10;
|
||||
public final static int BOOTSTRAP_STATUS_ONEHOP_CREATE = 15;
|
||||
public final static int BOOTSTRAP_STATUS_REQUESTING_STATUS = 20;
|
||||
public final static int BOOTSTRAP_STATUS_LOADING_STATUS = 25;
|
||||
public final static int BOOTSTRAP_STATUS_REQUESTING_KEYS = 35;
|
||||
public final static int BOOTSTRAP_STATUS_LOADING_KEYS = 40;
|
||||
public final static int BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS = 45;
|
||||
public final static int BOOTSTRAP_STATUS_LOADING_DESCRIPTORS = 50;
|
||||
public final static int BOOTSTRAP_STATUS_CONN_OR = 80;
|
||||
public final static int BOOTSTRAP_STATUS_HANDSHAKE_OR = 85;
|
||||
public final static int BOOTSTRAP_STATUS_CIRCUIT_CREATE = 90;
|
||||
public final static int BOOTSTRAP_STATUS_DONE = 100;
|
||||
|
||||
|
||||
private final static String implementation = "Orchid";
|
||||
private final static String version = "1.0.0";
|
||||
|
||||
private final static Charset defaultCharset = createDefaultCharset();
|
||||
|
||||
private static Charset createDefaultCharset() {
|
||||
return Charset.forName("ISO-8859-1");
|
||||
}
|
||||
|
||||
public static Charset getDefaultCharset() {
|
||||
return defaultCharset;
|
||||
}
|
||||
|
||||
public static String getBuildRevision() {
|
||||
return Revision.getBuildRevision();
|
||||
}
|
||||
|
||||
public static String getImplementation() {
|
||||
return implementation;
|
||||
}
|
||||
|
||||
public static String getFullVersion() {
|
||||
final String revision = getBuildRevision();
|
||||
if(revision == null || revision.isEmpty()) {
|
||||
return getVersion();
|
||||
} else {
|
||||
return getVersion() + "." + revision;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string describing the version of this software.
|
||||
*
|
||||
* @return A string representation of the software version.
|
||||
*/
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if running on Android by inspecting java.runtime.name property.
|
||||
*
|
||||
* @return True if running on Android.
|
||||
*/
|
||||
public static boolean isAndroidRuntime() {
|
||||
final String runtime = System.getProperty("java.runtime.name");
|
||||
return runtime != null && runtime.equals("Android Runtime");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new <code>TorConfig</code> instance.
|
||||
*
|
||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
||||
* before calling this method to create a <code>TorConfig</code>
|
||||
* @return A new <code>TorConfig</code> instance.
|
||||
* @see TorConfig
|
||||
*/
|
||||
static public TorConfig createConfig() {
|
||||
final TorConfig config = (TorConfig) Proxy.newProxyInstance(TorConfigProxy.class.getClassLoader(), new Class[] { TorConfig.class }, new TorConfigProxy());
|
||||
if(isAndroidRuntime()) {
|
||||
logger.warning("Android Runtime detected, disabling V2 Link protocol");
|
||||
config.setHandshakeV2Enabled(false);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
static public TorInitializationTracker createInitalizationTracker() {
|
||||
return new TorInitializationTracker();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new <code>Directory</code> instance.
|
||||
*
|
||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
||||
* before creating a <code>Directory</code>.
|
||||
* @param config This is a required dependency. You must create a <code>TorConfig</code> before
|
||||
* calling this method to create a <code>Directory</code>
|
||||
* @return A new <code>Directory</code> instance.
|
||||
* @see Directory
|
||||
*/
|
||||
static public Directory createDirectory(TorConfig config, DirectoryStore customDirectoryStore) {
|
||||
return new DirectoryImpl(config, customDirectoryStore);
|
||||
}
|
||||
|
||||
static public ConnectionCache createConnectionCache(TorConfig config, TorInitializationTracker tracker) {
|
||||
return new ConnectionCacheImpl(config, tracker);
|
||||
}
|
||||
/**
|
||||
* Create and return a new <code>CircuitManager</code> instance.
|
||||
*
|
||||
* @return A new <code>CircuitManager</code> instance.
|
||||
* @see CircuitManager
|
||||
*/
|
||||
static public CircuitManager createCircuitManager(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker tracker) {
|
||||
return new CircuitManagerImpl(config, directoryDownloader, directory, connectionCache, tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new <code>SocksPortListener</code> instance.
|
||||
*
|
||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
||||
* before calling this method to create a <code>SocksPortListener</code>.
|
||||
* @param circuitManager This is a required dependency. You must create a <code>CircuitManager</code>
|
||||
* before calling this method to create a <code>SocksPortListener</code>.
|
||||
* @return A new <code>SocksPortListener</code> instance.
|
||||
* @see SocksPortListener
|
||||
*/
|
||||
static public SocksPortListener createSocksPortListener(TorConfig config, CircuitManager circuitManager) {
|
||||
return new SocksPortListenerImpl(config, circuitManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new <code>DirectoryDownloader</code> instance.
|
||||
*
|
||||
* @param logManager This is a required dependency. You must create a <code>LogManager</code>
|
||||
* before calling this method to create a <code>DirectoryDownloader</code>.
|
||||
|
||||
* @param directory This is a required dependency. You must create a <code>Directory</code>
|
||||
* before calling this method to create a <code>DirectoryDownloader</code>
|
||||
*
|
||||
* @param circuitManager This is a required dependency. You must create a <code>CircuitManager</code>
|
||||
* before calling this method to create a <code>DirectoryDownloader</code>.
|
||||
*
|
||||
* @return A new <code>DirectoryDownloader</code> instance.
|
||||
* @see DirectoryDownloaderImpl
|
||||
*/
|
||||
static public DirectoryDownloaderImpl createDirectoryDownloader(TorConfig config, TorInitializationTracker initializationTracker) {
|
||||
return new DirectoryDownloaderImpl(config, initializationTracker);
|
||||
}
|
||||
}
|
221
src/java/com/subgraph/orchid/TorClient.java
Normal file
221
src/java/com/subgraph/orchid/TorClient.java
Normal file
@ -0,0 +1,221 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import com.subgraph.orchid.circuits.TorInitializationTracker;
|
||||
import com.subgraph.orchid.crypto.PRNGFixes;
|
||||
import com.subgraph.orchid.dashboard.Dashboard;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
||||
import com.subgraph.orchid.sockets.OrchidSocketFactory;
|
||||
|
||||
/**
|
||||
* This class is the main entry-point for running a Tor proxy
|
||||
* or client.
|
||||
*/
|
||||
public class TorClient {
|
||||
private final static Logger logger = Logger.getLogger(TorClient.class.getName());
|
||||
private final TorConfig config;
|
||||
private final Directory directory;
|
||||
private final TorInitializationTracker initializationTracker;
|
||||
private final ConnectionCache connectionCache;
|
||||
private final CircuitManager circuitManager;
|
||||
private final SocksPortListener socksListener;
|
||||
private final DirectoryDownloaderImpl directoryDownloader;
|
||||
private final Dashboard dashboard;
|
||||
|
||||
private boolean isStarted = false;
|
||||
private boolean isStopped = false;
|
||||
|
||||
private final CountDownLatch readyLatch;
|
||||
|
||||
public TorClient() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public TorClient(DirectoryStore customDirectoryStore) {
|
||||
if(Tor.isAndroidRuntime()) {
|
||||
PRNGFixes.apply();
|
||||
}
|
||||
config = Tor.createConfig();
|
||||
directory = Tor.createDirectory(config, customDirectoryStore);
|
||||
initializationTracker = Tor.createInitalizationTracker();
|
||||
initializationTracker.addListener(createReadyFlagInitializationListener());
|
||||
connectionCache = Tor.createConnectionCache(config, initializationTracker);
|
||||
directoryDownloader = Tor.createDirectoryDownloader(config, initializationTracker);
|
||||
circuitManager = Tor.createCircuitManager(config, directoryDownloader, directory, connectionCache, initializationTracker);
|
||||
socksListener = Tor.createSocksPortListener(config, circuitManager);
|
||||
readyLatch = new CountDownLatch(1);
|
||||
dashboard = new Dashboard();
|
||||
dashboard.addRenderables(circuitManager, directoryDownloader, socksListener);
|
||||
}
|
||||
|
||||
public TorConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public SocketFactory getSocketFactory() {
|
||||
return new OrchidSocketFactory(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start running the Tor client service.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
if(isStarted) {
|
||||
return;
|
||||
}
|
||||
if(isStopped) {
|
||||
throw new IllegalStateException("Cannot restart a TorClient instance. Create a new instance instead.");
|
||||
}
|
||||
logger.info("Starting Orchid (version: "+ Tor.getFullVersion() +")");
|
||||
verifyUnlimitedStrengthPolicyInstalled();
|
||||
directoryDownloader.start(directory);
|
||||
circuitManager.startBuildingCircuits();
|
||||
if(dashboard.isEnabledByProperty()) {
|
||||
dashboard.startListening();
|
||||
}
|
||||
isStarted = true;
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
if(!isStarted || isStopped) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
socksListener.stop();
|
||||
if(dashboard.isListening()) {
|
||||
dashboard.stopListening();
|
||||
}
|
||||
directoryDownloader.stop();
|
||||
circuitManager.stopBuildingCircuits(true);
|
||||
directory.close();
|
||||
connectionCache.close();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Unexpected exception while shutting down TorClient instance: "+ e, e);
|
||||
} finally {
|
||||
isStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
public Directory getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public ConnectionCache getConnectionCache() {
|
||||
return connectionCache;
|
||||
}
|
||||
|
||||
public CircuitManager getCircuitManager() {
|
||||
return circuitManager;
|
||||
}
|
||||
|
||||
public DirectoryDownloader getDirectoryDownloader() {
|
||||
return directoryDownloader;
|
||||
}
|
||||
|
||||
public void waitUntilReady() throws InterruptedException {
|
||||
readyLatch.await();
|
||||
}
|
||||
|
||||
public void waitUntilReady(long timeout) throws InterruptedException, TimeoutException {
|
||||
if(!readyLatch.await(timeout, TimeUnit.MILLISECONDS)) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
|
||||
public Stream openExitStreamTo(String hostname, int port) throws InterruptedException, TimeoutException, OpenFailedException {
|
||||
ensureStarted();
|
||||
return circuitManager.openExitStreamTo(hostname, port);
|
||||
}
|
||||
|
||||
private synchronized void ensureStarted() {
|
||||
if(!isStarted) {
|
||||
throw new IllegalStateException("Must call start() first");
|
||||
}
|
||||
}
|
||||
|
||||
public void enableSocksListener(int port) {
|
||||
socksListener.addListeningPort(port);
|
||||
}
|
||||
|
||||
public void enableSocksListener() {
|
||||
enableSocksListener(9150);
|
||||
}
|
||||
|
||||
public void enableDashboard() {
|
||||
if(!dashboard.isListening()) {
|
||||
dashboard.startListening();
|
||||
}
|
||||
}
|
||||
|
||||
public void enableDashboard(int port) {
|
||||
dashboard.setListeningPort(port);
|
||||
enableDashboard();
|
||||
}
|
||||
|
||||
public void disableDashboard() {
|
||||
if(dashboard.isListening()) {
|
||||
dashboard.stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
public void addInitializationListener(TorInitializationListener listener) {
|
||||
initializationTracker.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeInitializationListener(TorInitializationListener listener) {
|
||||
initializationTracker.removeListener(listener);
|
||||
}
|
||||
|
||||
private TorInitializationListener createReadyFlagInitializationListener() {
|
||||
return new TorInitializationListener() {
|
||||
public void initializationProgress(String message, int percent) {}
|
||||
public void initializationCompleted() {
|
||||
readyLatch.countDown();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
final TorClient client = new TorClient();
|
||||
client.addInitializationListener(createInitalizationListner());
|
||||
client.start();
|
||||
client.enableSocksListener();
|
||||
}
|
||||
|
||||
private static TorInitializationListener createInitalizationListner() {
|
||||
return new TorInitializationListener() {
|
||||
|
||||
public void initializationProgress(String message, int percent) {
|
||||
System.out.println(">>> [ "+ percent + "% ]: "+ message);
|
||||
}
|
||||
|
||||
public void initializationCompleted() {
|
||||
System.out.println("Tor is ready to go!");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void verifyUnlimitedStrengthPolicyInstalled() {
|
||||
try {
|
||||
if(Cipher.getMaxAllowedKeyLength("AES") < 256) {
|
||||
final String message = "Unlimited Strength Jurisdiction Policy Files are required but not installed.";
|
||||
logger.severe(message);
|
||||
throw new TorException(message);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.log(Level.SEVERE, "No AES provider found");
|
||||
throw new TorException(e);
|
||||
} catch (NoSuchMethodError e) {
|
||||
logger.info("Skipped check for Unlimited Strength Jurisdiction Policy Files");
|
||||
}
|
||||
}
|
||||
}
|
151
src/java/com/subgraph/orchid/TorConfig.java
Normal file
151
src/java/com/subgraph/orchid/TorConfig.java
Normal file
@ -0,0 +1,151 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie;
|
||||
import com.subgraph.orchid.config.TorConfigBridgeLine;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
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 "";
|
||||
}
|
||||
}
|
22
src/java/com/subgraph/orchid/TorException.java
Normal file
22
src/java/com/subgraph/orchid/TorException.java
Normal file
@ -0,0 +1,22 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public class TorException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 2462760291055303580L;
|
||||
|
||||
public TorException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TorException(String message, Throwable ex) {
|
||||
super(message, ex);
|
||||
}
|
||||
|
||||
public TorException(Throwable ex) {
|
||||
super(ex);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
public interface TorInitializationListener {
|
||||
void initializationProgress(String message, int percent);
|
||||
void initializationCompleted();
|
||||
}
|
14
src/java/com/subgraph/orchid/TorParsingException.java
Normal file
14
src/java/com/subgraph/orchid/TorParsingException.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
|
||||
public class TorParsingException extends TorException {
|
||||
public TorParsingException(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
public TorParsingException(String string, Throwable ex) {
|
||||
super(string, ex);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -4997757416476363399L;
|
||||
}
|
19
src/java/com/subgraph/orchid/VoteAuthorityEntry.java
Normal file
19
src/java/com/subgraph/orchid/VoteAuthorityEntry.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.subgraph.orchid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.directory.consensus.DirectorySignature;
|
||||
|
||||
public interface VoteAuthorityEntry {
|
||||
String getNickname();
|
||||
HexDigest getIdentity();
|
||||
String getHostname();
|
||||
IPv4Address getAddress();
|
||||
int getDirectoryPort();
|
||||
int getRouterPort();
|
||||
String getContact();
|
||||
HexDigest getVoteDigest();
|
||||
List<DirectorySignature> getSignatures();
|
||||
}
|
127
src/java/com/subgraph/orchid/circuits/CircuitBuildTask.java
Normal file
127
src/java/com/subgraph/orchid/circuits/CircuitBuildTask.java
Normal file
@ -0,0 +1,127 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.ConnectionCache;
|
||||
import com.subgraph.orchid.ConnectionFailedException;
|
||||
import com.subgraph.orchid.ConnectionHandshakeException;
|
||||
import com.subgraph.orchid.ConnectionTimeoutException;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
||||
|
||||
public class CircuitBuildTask implements Runnable {
|
||||
private final static Logger logger = Logger.getLogger(CircuitBuildTask.class.getName());
|
||||
private final CircuitCreationRequest creationRequest;
|
||||
private final ConnectionCache connectionCache;
|
||||
private final TorInitializationTracker initializationTracker;
|
||||
private final CircuitImpl circuit;
|
||||
private final CircuitExtender extender;
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
public CircuitBuildTask(CircuitCreationRequest request, ConnectionCache connectionCache, boolean ntorEnabled) {
|
||||
this(request, connectionCache, ntorEnabled, null);
|
||||
}
|
||||
|
||||
public CircuitBuildTask(CircuitCreationRequest request, ConnectionCache connectionCache, boolean ntorEnabled, TorInitializationTracker initializationTracker) {
|
||||
this.creationRequest = request;
|
||||
this.connectionCache = connectionCache;
|
||||
this.initializationTracker = initializationTracker;
|
||||
this.circuit = request.getCircuit();
|
||||
this.extender = new CircuitExtender(request.getCircuit(), ntorEnabled);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Router firstRouter = null;
|
||||
try {
|
||||
circuit.notifyCircuitBuildStart();
|
||||
creationRequest.choosePath();
|
||||
if(logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Opening a new circuit to "+ pathToString(creationRequest));
|
||||
}
|
||||
firstRouter = creationRequest.getPathElement(0);
|
||||
openEntryNodeConnection(firstRouter);
|
||||
buildCircuit(firstRouter);
|
||||
circuit.notifyCircuitBuildCompleted();
|
||||
} catch (ConnectionTimeoutException e) {
|
||||
connectionFailed("Timeout connecting to "+ firstRouter);
|
||||
} catch (ConnectionFailedException e) {
|
||||
connectionFailed("Connection failed to "+ firstRouter + " : " + e.getMessage());
|
||||
} catch (ConnectionHandshakeException e) {
|
||||
connectionFailed("Handshake error connecting to "+ firstRouter + " : " + e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
circuitBuildFailed("Circuit building thread interrupted");
|
||||
} catch(PathSelectionFailedException e) {
|
||||
circuitBuildFailed(e.getMessage());
|
||||
} catch (TorException e) {
|
||||
circuitBuildFailed(e.getMessage());
|
||||
} catch(Exception e) {
|
||||
circuitBuildFailed("Unexpected exception: "+ e);
|
||||
logger.log(Level.WARNING, "Unexpected exception while building circuit: "+ e, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String pathToString(CircuitCreationRequest ccr) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
for(Router r: ccr.getPath()) {
|
||||
if(sb.length() > 1)
|
||||
sb.append(",");
|
||||
sb.append(r.getNickname());
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void connectionFailed(String message) {
|
||||
creationRequest.connectionFailed(message);
|
||||
circuit.notifyCircuitBuildFailed();
|
||||
}
|
||||
|
||||
private void circuitBuildFailed(String message) {
|
||||
creationRequest.circuitBuildFailed(message);
|
||||
circuit.notifyCircuitBuildFailed();
|
||||
if(connection != null) {
|
||||
connection.removeCircuit(circuit);
|
||||
}
|
||||
}
|
||||
|
||||
private void openEntryNodeConnection(Router firstRouter) throws ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException, InterruptedException {
|
||||
connection = connectionCache.getConnectionTo(firstRouter, creationRequest.isDirectoryCircuit());
|
||||
circuit.bindToConnection(connection);
|
||||
creationRequest.connectionCompleted(connection);
|
||||
}
|
||||
|
||||
private void buildCircuit(Router firstRouter) throws TorException {
|
||||
notifyInitialization();
|
||||
final CircuitNode firstNode = extender.createFastTo(firstRouter);
|
||||
creationRequest.nodeAdded(firstNode);
|
||||
|
||||
for(int i = 1; i < creationRequest.getPathLength(); i++) {
|
||||
final CircuitNode extendedNode = extender.extendTo(creationRequest.getPathElement(i));
|
||||
creationRequest.nodeAdded(extendedNode);
|
||||
}
|
||||
creationRequest.circuitBuildCompleted(circuit);
|
||||
notifyDone();
|
||||
}
|
||||
|
||||
private void notifyInitialization() {
|
||||
if(initializationTracker != null) {
|
||||
final int event = creationRequest.isDirectoryCircuit() ?
|
||||
Tor.BOOTSTRAP_STATUS_ONEHOP_CREATE : Tor.BOOTSTRAP_STATUS_CIRCUIT_CREATE;
|
||||
initializationTracker.notifyEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDone() {
|
||||
if(initializationTracker != null && !creationRequest.isDirectoryCircuit()) {
|
||||
initializationTracker.notifyEvent(Tor.BOOTSTRAP_STATUS_DONE);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitBuildHandler;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
||||
|
||||
public class CircuitCreationRequest implements CircuitBuildHandler {
|
||||
private final CircuitImpl circuit;
|
||||
private final CircuitPathChooser pathChooser;
|
||||
private final CircuitBuildHandler buildHandler;
|
||||
private final boolean isDirectoryCircuit;
|
||||
|
||||
private List<Router> path;
|
||||
|
||||
public CircuitCreationRequest(CircuitPathChooser pathChooser, Circuit circuit, CircuitBuildHandler buildHandler, boolean isDirectoryCircuit) {
|
||||
this.pathChooser = pathChooser;
|
||||
this.circuit = (CircuitImpl) circuit;
|
||||
this.buildHandler = buildHandler;
|
||||
this.path = Collections.emptyList();
|
||||
this.isDirectoryCircuit = isDirectoryCircuit;
|
||||
}
|
||||
|
||||
void choosePath() throws InterruptedException, PathSelectionFailedException {
|
||||
if(!(circuit instanceof CircuitImpl)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
path = ((CircuitImpl)circuit).choosePath(pathChooser);
|
||||
|
||||
}
|
||||
|
||||
CircuitImpl getCircuit() {
|
||||
return circuit;
|
||||
}
|
||||
|
||||
List<Router> getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
int getPathLength() {
|
||||
return path.size();
|
||||
}
|
||||
|
||||
Router getPathElement(int idx) {
|
||||
return path.get(idx);
|
||||
}
|
||||
|
||||
CircuitBuildHandler getBuildHandler() {
|
||||
return buildHandler;
|
||||
}
|
||||
|
||||
boolean isDirectoryCircuit() {
|
||||
return isDirectoryCircuit;
|
||||
}
|
||||
|
||||
public void connectionCompleted(Connection connection) {
|
||||
if(buildHandler != null) {
|
||||
buildHandler.connectionCompleted(connection);
|
||||
}
|
||||
}
|
||||
|
||||
public void connectionFailed(String reason) {
|
||||
if(buildHandler != null) {
|
||||
buildHandler.connectionFailed(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public void nodeAdded(CircuitNode node) {
|
||||
if(buildHandler != null) {
|
||||
buildHandler.nodeAdded(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void circuitBuildCompleted(Circuit circuit) {
|
||||
if(buildHandler != null) {
|
||||
buildHandler.circuitBuildCompleted(circuit);
|
||||
}
|
||||
}
|
||||
|
||||
public void circuitBuildFailed(String reason) {
|
||||
if(buildHandler != null) {
|
||||
buildHandler.circuitBuildFailed(reason);
|
||||
}
|
||||
}
|
||||
}
|
292
src/java/com/subgraph/orchid/circuits/CircuitCreationTask.java
Normal file
292
src/java/com/subgraph/orchid/circuits/CircuitCreationTask.java
Normal file
@ -0,0 +1,292 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitBuildHandler;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.ConnectionCache;
|
||||
import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.ExitCircuit;
|
||||
import com.subgraph.orchid.InternalCircuit;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.CircuitManagerImpl.CircuitFilter;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
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 TorConfig config;
|
||||
private final Directory directory;
|
||||
private final ConnectionCache connectionCache;
|
||||
private final CircuitManagerImpl circuitManager;
|
||||
private final TorInitializationTracker initializationTracker;
|
||||
private final CircuitPathChooser pathChooser;
|
||||
private final Executor executor;
|
||||
private final CircuitBuildHandler buildHandler;
|
||||
private final CircuitBuildHandler internalBuildHandler;
|
||||
// To avoid obnoxiously printing a warning every second
|
||||
private int notEnoughDirectoryInformationWarningCounter = 0;
|
||||
|
||||
private final CircuitPredictor predictor;
|
||||
|
||||
private final AtomicLong lastNewCircuit;
|
||||
|
||||
CircuitCreationTask(TorConfig config, Directory directory, ConnectionCache connectionCache, CircuitPathChooser pathChooser, CircuitManagerImpl circuitManager, TorInitializationTracker initializationTracker) {
|
||||
this.config = config;
|
||||
this.directory = directory;
|
||||
this.connectionCache = connectionCache;
|
||||
this.circuitManager = circuitManager;
|
||||
this.initializationTracker = initializationTracker;
|
||||
this.pathChooser = pathChooser;
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
this.buildHandler = createCircuitBuildHandler();
|
||||
this.internalBuildHandler = createInternalCircuitBuildHandler();
|
||||
this.predictor = new CircuitPredictor();
|
||||
this.lastNewCircuit = new AtomicLong();
|
||||
}
|
||||
|
||||
CircuitPredictor getCircuitPredictor() {
|
||||
return predictor;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
expireOldCircuits();
|
||||
assignPendingStreamsToActiveCircuits();
|
||||
checkExpiredPendingCircuits();
|
||||
checkCircuitsForCreation();
|
||||
}
|
||||
|
||||
void predictPort(int port) {
|
||||
predictor.addExitPortRequest(port);
|
||||
}
|
||||
|
||||
private void assignPendingStreamsToActiveCircuits() {
|
||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
||||
if(pendingExitStreams.isEmpty())
|
||||
return;
|
||||
|
||||
for(ExitCircuit c: circuitManager.getRandomlyOrderedListOfExitCircuits()) {
|
||||
final Iterator<StreamExitRequest> it = pendingExitStreams.iterator();
|
||||
while(it.hasNext()) {
|
||||
if(attemptHandleStreamRequest(c, it.next()))
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean attemptHandleStreamRequest(ExitCircuit c, StreamExitRequest request) {
|
||||
if(c.canHandleExitTo(request)) {
|
||||
if(request.reserveRequest()) {
|
||||
launchExitStreamTask(c, request);
|
||||
}
|
||||
// else request is reserved meaning another circuit is already trying to handle it
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void launchExitStreamTask(ExitCircuit circuit, StreamExitRequest exitRequest) {
|
||||
final OpenExitStreamTask task = new OpenExitStreamTask(circuit, exitRequest);
|
||||
executor.execute(task);
|
||||
}
|
||||
|
||||
private void expireOldCircuits() {
|
||||
final Set<Circuit> circuits = circuitManager.getCircuitsByFilter(new CircuitFilter() {
|
||||
|
||||
public boolean filter(Circuit circuit) {
|
||||
return !circuit.isMarkedForClose() && circuit.getSecondsDirty() > MAX_CIRCUIT_DIRTINESS;
|
||||
}
|
||||
});
|
||||
for(Circuit c: circuits) {
|
||||
logger.fine("Closing idle dirty circuit: "+ c);
|
||||
((CircuitImpl)c).markForClose();
|
||||
}
|
||||
}
|
||||
private void checkExpiredPendingCircuits() {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
private void checkCircuitsForCreation() {
|
||||
|
||||
if(!directory.haveMinimumRouterInfo()) {
|
||||
if(notEnoughDirectoryInformationWarningCounter % 20 == 0)
|
||||
logger.info("Cannot build circuits because we don't have enough directory information");
|
||||
notEnoughDirectoryInformationWarningCounter++;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(lastNewCircuit.get() != 0) {
|
||||
final long now = System.currentTimeMillis();
|
||||
if((now - lastNewCircuit.get()) < config.getNewCircuitPeriod()) {
|
||||
// return;
|
||||
}
|
||||
}
|
||||
|
||||
buildCircuitIfNeeded();
|
||||
maybeBuildInternalCircuit();
|
||||
}
|
||||
|
||||
private void buildCircuitIfNeeded() {
|
||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
||||
final List<PredictedPortTarget> predictedPorts = predictor.getPredictedPortTargets();
|
||||
final List<ExitTarget> exitTargets = new ArrayList<ExitTarget>();
|
||||
for(StreamExitRequest streamRequest: pendingExitStreams) {
|
||||
if(!streamRequest.isReserved() && countCircuitsSupportingTarget(streamRequest, false) == 0) {
|
||||
exitTargets.add(streamRequest);
|
||||
}
|
||||
}
|
||||
for(PredictedPortTarget ppt: predictedPorts) {
|
||||
if(countCircuitsSupportingTarget(ppt, true) < 2) {
|
||||
exitTargets.add(ppt);
|
||||
}
|
||||
}
|
||||
buildCircuitToHandleExitTargets(exitTargets);
|
||||
}
|
||||
|
||||
private void maybeBuildInternalCircuit() {
|
||||
final int needed = circuitManager.getNeededCleanCircuitCount(predictor.isInternalPredicted());
|
||||
|
||||
if(needed > 0) {
|
||||
launchBuildTaskForInternalCircuit();
|
||||
}
|
||||
}
|
||||
|
||||
private void launchBuildTaskForInternalCircuit() {
|
||||
logger.fine("Launching new internal circuit");
|
||||
final InternalCircuitImpl circuit = new InternalCircuitImpl(circuitManager);
|
||||
final CircuitCreationRequest request = new CircuitCreationRequest(pathChooser, circuit, internalBuildHandler, false);
|
||||
final CircuitBuildTask task = new CircuitBuildTask(request, connectionCache, circuitManager.isNtorEnabled());
|
||||
executor.execute(task);
|
||||
circuitManager.incrementPendingInternalCircuitCount();
|
||||
}
|
||||
|
||||
private int countCircuitsSupportingTarget(final ExitTarget target, final boolean needClean) {
|
||||
final CircuitFilter filter = new CircuitFilter() {
|
||||
public boolean filter(Circuit circuit) {
|
||||
if(!(circuit instanceof ExitCircuit)) {
|
||||
return false;
|
||||
}
|
||||
final ExitCircuit ec = (ExitCircuit) circuit;
|
||||
final boolean pendingOrConnected = circuit.isPending() || circuit.isConnected();
|
||||
final boolean isCleanIfNeeded = !(needClean && !circuit.isClean());
|
||||
return pendingOrConnected && isCleanIfNeeded && ec.canHandleExitTo(target);
|
||||
}
|
||||
};
|
||||
return circuitManager.getCircuitsByFilter(filter).size();
|
||||
}
|
||||
|
||||
private void buildCircuitToHandleExitTargets(List<ExitTarget> exitTargets) {
|
||||
if(exitTargets.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if(!directory.haveMinimumRouterInfo())
|
||||
return;
|
||||
if(circuitManager.getPendingCircuitCount() >= MAX_PENDING_CIRCUITS)
|
||||
return;
|
||||
|
||||
if(logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Building new circuit to handle "+ exitTargets.size() +" pending streams and predicted ports");
|
||||
}
|
||||
|
||||
launchBuildTaskForTargets(exitTargets);
|
||||
}
|
||||
|
||||
private void launchBuildTaskForTargets(List<ExitTarget> exitTargets) {
|
||||
final Router exitRouter = pathChooser.chooseExitNodeForTargets(exitTargets);
|
||||
if(exitRouter == null) {
|
||||
logger.warning("Failed to select suitable exit node for targets");
|
||||
return;
|
||||
}
|
||||
|
||||
final Circuit circuit = circuitManager.createNewExitCircuit(exitRouter);
|
||||
final CircuitCreationRequest request = new CircuitCreationRequest(pathChooser, circuit, buildHandler, false);
|
||||
final CircuitBuildTask task = new CircuitBuildTask(request, connectionCache, circuitManager.isNtorEnabled(), initializationTracker);
|
||||
executor.execute(task);
|
||||
}
|
||||
|
||||
private CircuitBuildHandler createCircuitBuildHandler() {
|
||||
return new CircuitBuildHandler() {
|
||||
|
||||
public void circuitBuildCompleted(Circuit circuit) {
|
||||
logger.fine("Circuit completed to: "+ circuit);
|
||||
circuitOpenedHandler(circuit);
|
||||
lastNewCircuit.set(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void circuitBuildFailed(String reason) {
|
||||
logger.fine("Circuit build failed: "+ reason);
|
||||
buildCircuitIfNeeded();
|
||||
}
|
||||
|
||||
public void connectionCompleted(Connection connection) {
|
||||
logger.finer("Circuit connection completed to "+ connection);
|
||||
}
|
||||
|
||||
public void connectionFailed(String reason) {
|
||||
logger.fine("Circuit connection failed: "+ reason);
|
||||
buildCircuitIfNeeded();
|
||||
}
|
||||
|
||||
public void nodeAdded(CircuitNode node) {
|
||||
logger.finer("Node added to circuit: "+ node);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void circuitOpenedHandler(Circuit circuit) {
|
||||
if(!(circuit instanceof ExitCircuit)) {
|
||||
return;
|
||||
}
|
||||
final ExitCircuit ec = (ExitCircuit) circuit;
|
||||
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
|
||||
for(StreamExitRequest req: pendingExitStreams) {
|
||||
if(ec.canHandleExitTo(req) && req.reserveRequest()) {
|
||||
launchExitStreamTask(ec, req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CircuitBuildHandler createInternalCircuitBuildHandler() {
|
||||
return new CircuitBuildHandler() {
|
||||
|
||||
public void nodeAdded(CircuitNode node) {
|
||||
logger.finer("Node added to internal circuit: "+ node);
|
||||
}
|
||||
|
||||
public void connectionFailed(String reason) {
|
||||
logger.fine("Circuit connection failed: "+ reason);
|
||||
circuitManager.decrementPendingInternalCircuitCount();
|
||||
}
|
||||
|
||||
public void connectionCompleted(Connection connection) {
|
||||
logger.finer("Circuit connection completed to "+ connection);
|
||||
}
|
||||
|
||||
public void circuitBuildFailed(String reason) {
|
||||
logger.fine("Circuit build failed: "+ reason);
|
||||
circuitManager.decrementPendingInternalCircuitCount();
|
||||
}
|
||||
|
||||
public void circuitBuildCompleted(Circuit circuit) {
|
||||
logger.fine("Internal circuit build completed: "+ circuit);
|
||||
lastNewCircuit.set(System.currentTimeMillis());
|
||||
circuitManager.addCleanInternalCircuit((InternalCircuit) circuit);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
155
src/java/com/subgraph/orchid/circuits/CircuitExtender.java
Normal file
155
src/java/com/subgraph/orchid/circuits/CircuitExtender.java
Normal file
@ -0,0 +1,155 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.cells.CellImpl;
|
||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
||||
import com.subgraph.orchid.crypto.TorCreateFastKeyAgreement;
|
||||
import com.subgraph.orchid.crypto.TorKeyAgreement;
|
||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
||||
import com.subgraph.orchid.crypto.TorStreamCipher;
|
||||
|
||||
public class CircuitExtender {
|
||||
private final static Logger logger = Logger.getLogger(CircuitExtender.class.getName());
|
||||
|
||||
private final static int DH_BYTES = 1024 / 8;
|
||||
private final static int PKCS1_OAEP_PADDING_OVERHEAD = 42;
|
||||
private final static int CIPHER_KEY_LEN = TorStreamCipher.KEY_LEN;
|
||||
final static int TAP_ONIONSKIN_LEN = PKCS1_OAEP_PADDING_OVERHEAD + CIPHER_KEY_LEN + DH_BYTES;
|
||||
final static int TAP_ONIONSKIN_REPLY_LEN = DH_BYTES + TorMessageDigest.TOR_DIGEST_SIZE;
|
||||
|
||||
|
||||
private final CircuitImpl circuit;
|
||||
private final boolean ntorEnabled;
|
||||
|
||||
|
||||
CircuitExtender(CircuitImpl circuit, boolean ntorEnabled) {
|
||||
this.circuit = circuit;
|
||||
this.ntorEnabled = ntorEnabled;
|
||||
}
|
||||
|
||||
|
||||
CircuitNode createFastTo(Router targetRouter) {
|
||||
logger.fine("Creating 'fast' to "+ targetRouter);
|
||||
final TorCreateFastKeyAgreement kex = new TorCreateFastKeyAgreement();
|
||||
sendCreateFastCell(kex);
|
||||
return receiveAndProcessCreateFastResponse(targetRouter, kex);
|
||||
}
|
||||
|
||||
private void sendCreateFastCell(TorCreateFastKeyAgreement kex) {
|
||||
final Cell cell = CellImpl.createCell(circuit.getCircuitId(), Cell.CREATE_FAST);
|
||||
cell.putByteArray(kex.createOnionSkin());
|
||||
circuit.sendCell(cell);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return processCreatedFastCell(targetRouter, cell, kex);
|
||||
}
|
||||
|
||||
private CircuitNode processCreatedFastCell(Router targetRouter, Cell cell, TorKeyAgreement kex) {
|
||||
final byte[] payload = new byte[TorMessageDigest.TOR_DIGEST_SIZE * 2];
|
||||
final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE];
|
||||
final byte[] verifyHash = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
||||
cell.getByteArray(payload);
|
||||
if(!kex.deriveKeysFromHandshakeResponse(payload, keyMaterial, verifyHash)) {
|
||||
// XXX
|
||||
return null;
|
||||
}
|
||||
final CircuitNode node = CircuitNodeImpl.createFirstHop(targetRouter, keyMaterial, verifyHash);
|
||||
circuit.appendNode(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
CircuitNode extendTo(Router targetRouter) {
|
||||
if(circuit.getCircuitLength() == 0) {
|
||||
throw new TorException("Cannot EXTEND an empty circuit");
|
||||
}
|
||||
|
||||
if(useNtor(targetRouter)) {
|
||||
final NTorCircuitExtender nce = new NTorCircuitExtender(this, targetRouter);
|
||||
return nce.extendTo();
|
||||
} else {
|
||||
final TapCircuitExtender tce = new TapCircuitExtender(this, targetRouter);
|
||||
return tce.extendTo();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean useNtor(Router targetRouter) {
|
||||
return ntorEnabled && targetRouter.getNTorOnionKey() != null;
|
||||
}
|
||||
|
||||
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 +"]");
|
||||
}
|
||||
|
||||
private String nodeToName(CircuitNode node) {
|
||||
if(node == null || node.getRouter() == null) {
|
||||
return "(null)";
|
||||
}
|
||||
final Router router = node.getRouter();
|
||||
return router.getNickname();
|
||||
}
|
||||
|
||||
|
||||
public void sendRelayCell(RelayCell cell) {
|
||||
circuit.sendRelayCell(cell);
|
||||
}
|
||||
|
||||
|
||||
public RelayCell receiveRelayResponse(int expectedCommand, Router extendTarget) {
|
||||
final RelayCell cell = circuit.receiveRelayCell();
|
||||
if(cell == null) {
|
||||
throw new TorException("Timeout building circuit");
|
||||
}
|
||||
final int command = cell.getRelayCommand();
|
||||
if(command == RelayCell.RELAY_TRUNCATED) {
|
||||
final int code = cell.getByte() & 0xFF;
|
||||
final String msg = CellImpl.errorToDescription(code);
|
||||
final String source = nodeToName(cell.getCircuitNode());
|
||||
if(code == Cell.ERROR_PROTOCOL) {
|
||||
logProtocolViolation(source, extendTarget);
|
||||
}
|
||||
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);
|
||||
throw new TorException("Received incorrect extend response, expecting "+ expected + " but received "+ received);
|
||||
} else {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
circuit.appendNode(node);
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
public RelayCell createRelayCell(int command) {
|
||||
return new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), 0, command, true);
|
||||
}
|
||||
|
||||
Router getFinalRouter() {
|
||||
final CircuitNode node = circuit.getFinalCircuitNode();
|
||||
if(node != null) {
|
||||
return node.getRouter();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
320
src/java/com/subgraph/orchid/circuits/CircuitIO.java
Normal file
320
src/java/com/subgraph/orchid/circuits/CircuitIO.java
Normal file
@ -0,0 +1,320 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.ConnectionIOException;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.cells.CellImpl;
|
||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
||||
|
||||
public class CircuitIO implements DashboardRenderable {
|
||||
private static final Logger logger = Logger.getLogger(CircuitIO.class.getName());
|
||||
private final static long CIRCUIT_BUILD_TIMEOUT_MS = 30 * 1000;
|
||||
private final static long CIRCUIT_RELAY_RESPONSE_TIMEOUT = 20 * 1000;
|
||||
|
||||
private final CircuitImpl circuit;
|
||||
private final Connection connection;
|
||||
private final int circuitId;
|
||||
|
||||
private final BlockingQueue<RelayCell> relayCellResponseQueue;
|
||||
private final BlockingQueue<Cell> controlCellResponseQueue;
|
||||
private final Map<Integer, StreamImpl> streamMap;
|
||||
private final Object relaySendLock = new Object();
|
||||
|
||||
private boolean isMarkedForClose;
|
||||
private boolean isClosed;
|
||||
|
||||
CircuitIO(CircuitImpl circuit, Connection connection, int circuitId) {
|
||||
this.circuit = circuit;
|
||||
this.connection = connection;
|
||||
this.circuitId = circuitId;
|
||||
|
||||
this.relayCellResponseQueue = new LinkedBlockingQueue<RelayCell>();
|
||||
this.controlCellResponseQueue = new LinkedBlockingQueue<Cell>();
|
||||
this.streamMap = new HashMap<Integer, StreamImpl>();
|
||||
}
|
||||
|
||||
Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
int getCircuitId() {
|
||||
return circuitId;
|
||||
}
|
||||
|
||||
RelayCell dequeueRelayResponseCell() {
|
||||
try {
|
||||
final long timeout = getReceiveTimeout();
|
||||
return relayCellResponseQueue.poll(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private RelayCell decryptRelayCell(Cell cell) {
|
||||
for(CircuitNode node: circuit.getNodeList()) {
|
||||
if(node.decryptBackwardCell(cell)) {
|
||||
return RelayCellImpl.createFromCell(node, cell);
|
||||
}
|
||||
}
|
||||
destroyCircuit();
|
||||
throw new TorException("Could not decrypt relay cell");
|
||||
}
|
||||
|
||||
// Return null on timeout
|
||||
Cell receiveControlCellResponse() {
|
||||
try {
|
||||
final long timeout = getReceiveTimeout();
|
||||
return controlCellResponseQueue.poll(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private long getReceiveTimeout() {
|
||||
if(circuit.getStatus().isBuilding())
|
||||
return remainingBuildTime();
|
||||
else
|
||||
return CIRCUIT_RELAY_RESPONSE_TIMEOUT;
|
||||
}
|
||||
|
||||
private long remainingBuildTime() {
|
||||
final long elapsed = circuit.getStatus().getMillisecondsElapsedSinceCreated();
|
||||
if(elapsed == 0 || elapsed >= CIRCUIT_BUILD_TIMEOUT_MS)
|
||||
return 0;
|
||||
return CIRCUIT_BUILD_TIMEOUT_MS - elapsed;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called by the cell reading thread in ConnectionImpl to deliver control cells
|
||||
* associated with this circuit (CREATED, CREATED_FAST, or DESTROY).
|
||||
*/
|
||||
void deliverControlCell(Cell cell) {
|
||||
if(cell.getCommand() == Cell.DESTROY) {
|
||||
processDestroyCell(cell.getByte());
|
||||
} else {
|
||||
controlCellResponseQueue.add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDestroyCell(int reason) {
|
||||
logger.fine("DESTROY cell received ("+ CellImpl.errorToDescription(reason) +") on "+ circuit);
|
||||
destroyCircuit();
|
||||
}
|
||||
|
||||
/* This is called by the cell reading thread in ConnectionImpl to deliver RELAY cells. */
|
||||
void deliverRelayCell(Cell cell) {
|
||||
circuit.getStatus().updateDirtyTimestamp();
|
||||
final RelayCell relayCell = decryptRelayCell(cell);
|
||||
logRelayCell("Dispatching: ", relayCell);
|
||||
switch(relayCell.getRelayCommand()) {
|
||||
case RelayCell.RELAY_EXTENDED:
|
||||
case RelayCell.RELAY_EXTENDED2:
|
||||
case RelayCell.RELAY_RESOLVED:
|
||||
case RelayCell.RELAY_TRUNCATED:
|
||||
case RelayCell.RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
|
||||
case RelayCell.RELAY_COMMAND_INTRODUCE_ACK:
|
||||
case RelayCell.RELAY_COMMAND_RENDEZVOUS2:
|
||||
relayCellResponseQueue.add(relayCell);
|
||||
break;
|
||||
case RelayCell.RELAY_DATA:
|
||||
case RelayCell.RELAY_END:
|
||||
case RelayCell.RELAY_CONNECTED:
|
||||
processRelayDataCell(relayCell);
|
||||
break;
|
||||
|
||||
case RelayCell.RELAY_SENDME:
|
||||
if(relayCell.getStreamId() != 0)
|
||||
processRelayDataCell(relayCell);
|
||||
else
|
||||
processCircuitSendme(relayCell);
|
||||
break;
|
||||
case RelayCell.RELAY_BEGIN:
|
||||
case RelayCell.RELAY_BEGIN_DIR:
|
||||
case RelayCell.RELAY_EXTEND:
|
||||
case RelayCell.RELAY_RESOLVE:
|
||||
case RelayCell.RELAY_TRUNCATE:
|
||||
destroyCircuit();
|
||||
throw new TorException("Unexpected 'forward' direction relay cell type: "+ relayCell.getRelayCommand());
|
||||
}
|
||||
}
|
||||
|
||||
/* Runs in the context of the connection cell reading thread */
|
||||
private void processRelayDataCell(RelayCell cell) {
|
||||
if(cell.getRelayCommand() == RelayCell.RELAY_DATA) {
|
||||
cell.getCircuitNode().decrementDeliverWindow();
|
||||
if(cell.getCircuitNode().considerSendingSendme()) {
|
||||
final RelayCell sendme = createRelayCell(RelayCell.RELAY_SENDME, 0, cell.getCircuitNode());
|
||||
sendRelayCellTo(sendme, sendme.getCircuitNode());
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(streamMap) {
|
||||
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
|
||||
// will send a RELAY_END for this stream.
|
||||
if(stream != null) {
|
||||
stream.addInputCell(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode) {
|
||||
return new RelayCellImpl(targetNode, circuitId, streamId, relayCommand);
|
||||
}
|
||||
|
||||
void sendRelayCellTo(RelayCell cell, CircuitNode targetNode) {
|
||||
synchronized(relaySendLock) {
|
||||
logRelayCell("Sending: ", cell);
|
||||
cell.setLength();
|
||||
targetNode.updateForwardDigest(cell);
|
||||
cell.setDigest(targetNode.getForwardDigestBytes());
|
||||
|
||||
for(CircuitNode node = targetNode; node != null; node = node.getPreviousNode())
|
||||
node.encryptForwardCell(cell);
|
||||
|
||||
if(cell.getRelayCommand() == RelayCell.RELAY_DATA)
|
||||
targetNode.waitForSendWindowAndDecrement();
|
||||
|
||||
sendCell(cell);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void logRelayCell(String message, RelayCell cell) {
|
||||
final Level level = getLogLevelForCell(cell);
|
||||
if(!logger.isLoggable(level)) {
|
||||
return;
|
||||
}
|
||||
logger.log(level, message + cell);
|
||||
}
|
||||
|
||||
private Level getLogLevelForCell(RelayCell cell) {
|
||||
switch(cell.getRelayCommand()) {
|
||||
case RelayCell.RELAY_DATA:
|
||||
case RelayCell.RELAY_SENDME:
|
||||
return Level.FINEST;
|
||||
default:
|
||||
return Level.FINER;
|
||||
}
|
||||
}
|
||||
|
||||
void sendCell(Cell cell) {
|
||||
final CircuitStatus status = circuit.getStatus();
|
||||
if(!(status.isConnected() || status.isBuilding()))
|
||||
return;
|
||||
try {
|
||||
status.updateDirtyTimestamp();
|
||||
connection.sendCell(cell);
|
||||
} catch (ConnectionIOException e) {
|
||||
destroyCircuit();
|
||||
}
|
||||
}
|
||||
|
||||
void markForClose() {
|
||||
synchronized (streamMap) {
|
||||
if(isMarkedForClose) {
|
||||
return;
|
||||
}
|
||||
isMarkedForClose = true;
|
||||
if(streamMap.isEmpty()) {
|
||||
closeCircuit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isMarkedForClose() {
|
||||
return isMarkedForClose;
|
||||
}
|
||||
|
||||
private void closeCircuit() {
|
||||
logger.fine("Closing circuit "+ circuit);
|
||||
sendDestroyCell(Cell.ERROR_NONE);
|
||||
connection.removeCircuit(circuit);
|
||||
circuit.setStateDestroyed();
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
void sendDestroyCell(int reason) {
|
||||
Cell destroy = CellImpl.createCell(circuitId, Cell.DESTROY);
|
||||
destroy.putByte(reason);
|
||||
try {
|
||||
connection.sendCell(destroy);
|
||||
} catch (ConnectionIOException e) {
|
||||
logger.warning("Connection IO error sending DESTROY cell: "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void processCircuitSendme(RelayCell cell) {
|
||||
cell.getCircuitNode().incrementSendWindow();
|
||||
}
|
||||
|
||||
void destroyCircuit() {
|
||||
synchronized(streamMap) {
|
||||
if(isClosed) {
|
||||
return;
|
||||
}
|
||||
circuit.setStateDestroyed();
|
||||
connection.removeCircuit(circuit);
|
||||
final List<StreamImpl> tmpList = new ArrayList<StreamImpl>(streamMap.values());
|
||||
for(StreamImpl s: tmpList) {
|
||||
s.close();
|
||||
}
|
||||
isClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
StreamImpl createNewStream(boolean autoclose) {
|
||||
synchronized(streamMap) {
|
||||
final int streamId = circuit.getStatus().nextStreamId();
|
||||
final StreamImpl stream = new StreamImpl(circuit, circuit.getFinalCircuitNode(), streamId, autoclose);
|
||||
streamMap.put(streamId, stream);
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
void removeStream(StreamImpl stream) {
|
||||
synchronized(streamMap) {
|
||||
streamMap.remove(stream.getStreamId());
|
||||
if(streamMap.isEmpty() && isMarkedForClose) {
|
||||
closeCircuit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Stream> getActiveStreams() {
|
||||
synchronized (streamMap) {
|
||||
return new ArrayList<Stream>(streamMap.values());
|
||||
}
|
||||
}
|
||||
|
||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
||||
if((flags & DASHBOARD_STREAMS) == 0) {
|
||||
return;
|
||||
}
|
||||
for(Stream s: getActiveStreams()) {
|
||||
renderer.renderComponent(writer, flags, s);
|
||||
}
|
||||
}
|
||||
}
|
286
src/java/com/subgraph/orchid/circuits/CircuitImpl.java
Normal file
286
src/java/com/subgraph/orchid/circuits/CircuitImpl.java
Normal file
@ -0,0 +1,286 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.DirectoryCircuit;
|
||||
import com.subgraph.orchid.ExitCircuit;
|
||||
import com.subgraph.orchid.InternalCircuit;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
||||
|
||||
/**
|
||||
* This class represents an established circuit through the Tor network.
|
||||
*
|
||||
*/
|
||||
public abstract class CircuitImpl implements Circuit, DashboardRenderable {
|
||||
protected final static Logger logger = Logger.getLogger(CircuitImpl.class.getName());
|
||||
|
||||
static ExitCircuit createExitCircuit(CircuitManagerImpl circuitManager, Router exitRouter) {
|
||||
return new ExitCircuitImpl(circuitManager, exitRouter);
|
||||
}
|
||||
|
||||
static ExitCircuit createExitCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
return new ExitCircuitImpl(circuitManager, prechosenPath);
|
||||
}
|
||||
|
||||
static DirectoryCircuit createDirectoryCircuit(CircuitManagerImpl circuitManager) {
|
||||
return new DirectoryCircuitImpl(circuitManager, null);
|
||||
}
|
||||
|
||||
static DirectoryCircuit createDirectoryCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
return new DirectoryCircuitImpl(circuitManager, prechosenPath);
|
||||
}
|
||||
|
||||
static InternalCircuit createInternalCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
return new InternalCircuitImpl(circuitManager, prechosenPath);
|
||||
}
|
||||
|
||||
private final CircuitManagerImpl circuitManager;
|
||||
protected final List<Router> prechosenPath;
|
||||
|
||||
private final List<CircuitNode> nodeList;
|
||||
private final CircuitStatus status;
|
||||
|
||||
private CircuitIO io;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected CircuitImpl(CircuitManagerImpl circuitManager) {
|
||||
this(circuitManager, null);
|
||||
}
|
||||
|
||||
protected CircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
nodeList = new ArrayList<CircuitNode>();
|
||||
this.circuitManager = circuitManager;
|
||||
this.prechosenPath = prechosenPath;
|
||||
status = new CircuitStatus();
|
||||
}
|
||||
|
||||
List<Router> choosePath(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
|
||||
if(prechosenPath != null) {
|
||||
return new ArrayList<Router>(prechosenPath);
|
||||
} else {
|
||||
return choosePathForCircuit(pathChooser);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException;
|
||||
|
||||
void bindToConnection(Connection connection) {
|
||||
if(io != null) {
|
||||
throw new IllegalStateException("Circuit already bound to a connection");
|
||||
}
|
||||
final int id = connection.bindCircuit(this);
|
||||
io = new CircuitIO(this, connection, id);
|
||||
}
|
||||
|
||||
public void markForClose() {
|
||||
if(io != null) {
|
||||
io.markForClose();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMarkedForClose() {
|
||||
if(io == null) {
|
||||
return false;
|
||||
} else {
|
||||
return io.isMarkedForClose();
|
||||
}
|
||||
}
|
||||
|
||||
CircuitStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return status.isConnected();
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return status.isBuilding();
|
||||
}
|
||||
|
||||
public boolean isClean() {
|
||||
return !status.isDirty();
|
||||
}
|
||||
|
||||
public int getSecondsDirty() {
|
||||
return (int) (status.getMillisecondsDirty() / 1000);
|
||||
}
|
||||
|
||||
void notifyCircuitBuildStart() {
|
||||
if(!status.isUnconnected()) {
|
||||
throw new IllegalStateException("Can only connect UNCONNECTED circuits");
|
||||
}
|
||||
status.updateCreatedTimestamp();
|
||||
status.setStateBuilding();
|
||||
circuitManager.addActiveCircuit(this);
|
||||
}
|
||||
|
||||
void notifyCircuitBuildFailed() {
|
||||
status.setStateFailed();
|
||||
circuitManager.removeActiveCircuit(this);
|
||||
}
|
||||
|
||||
void notifyCircuitBuildCompleted() {
|
||||
status.setStateOpen();
|
||||
status.updateCreatedTimestamp();
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
if(!isConnected())
|
||||
throw new TorException("Circuit is not connected.");
|
||||
return io.getConnection();
|
||||
}
|
||||
|
||||
public int getCircuitId() {
|
||||
if(io == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return io.getCircuitId();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendRelayCell(RelayCell cell) {
|
||||
io.sendRelayCellTo(cell, cell.getCircuitNode());
|
||||
}
|
||||
|
||||
public void sendRelayCellToFinalNode(RelayCell cell) {
|
||||
io.sendRelayCellTo(cell, getFinalCircuitNode());
|
||||
}
|
||||
|
||||
public void appendNode(CircuitNode node) {
|
||||
nodeList.add(node);
|
||||
}
|
||||
|
||||
List<CircuitNode> getNodeList() {
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
int getCircuitLength() {
|
||||
return nodeList.size();
|
||||
}
|
||||
|
||||
public CircuitNode getFinalCircuitNode() {
|
||||
if(nodeList.isEmpty())
|
||||
throw new TorException("getFinalCircuitNode() called on empty circuit");
|
||||
return nodeList.get( getCircuitLength() - 1);
|
||||
}
|
||||
|
||||
public RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode) {
|
||||
return io.createRelayCell(relayCommand, streamId, targetNode);
|
||||
}
|
||||
|
||||
public RelayCell receiveRelayCell() {
|
||||
return io.dequeueRelayResponseCell();
|
||||
}
|
||||
|
||||
void sendCell(Cell cell) {
|
||||
io.sendCell(cell);
|
||||
}
|
||||
|
||||
Cell receiveControlCellResponse() {
|
||||
return io.receiveControlCellResponse();
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called by the cell reading thread in ConnectionImpl to deliver control cells
|
||||
* associated with this circuit (CREATED or CREATED_FAST).
|
||||
*/
|
||||
public void deliverControlCell(Cell cell) {
|
||||
io.deliverControlCell(cell);
|
||||
}
|
||||
|
||||
/* This is called by the cell reading thread in ConnectionImpl to deliver RELAY cells. */
|
||||
public void deliverRelayCell(Cell cell) {
|
||||
io.deliverRelayCell(cell);
|
||||
}
|
||||
|
||||
protected StreamImpl createNewStream(boolean autoclose) {
|
||||
return io.createNewStream(autoclose);
|
||||
}
|
||||
protected StreamImpl createNewStream() {
|
||||
return createNewStream(false);
|
||||
}
|
||||
|
||||
void setStateDestroyed() {
|
||||
status.setStateDestroyed();
|
||||
circuitManager.removeActiveCircuit(this);
|
||||
}
|
||||
|
||||
public void destroyCircuit() {
|
||||
io.destroyCircuit();
|
||||
circuitManager.removeActiveCircuit(this);
|
||||
}
|
||||
|
||||
|
||||
public void removeStream(StreamImpl stream) {
|
||||
io.removeStream(stream);
|
||||
}
|
||||
|
||||
protected Stream processStreamOpenException(Exception e) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
if(e instanceof InterruptedException) {
|
||||
throw (InterruptedException) e;
|
||||
} else if(e instanceof TimeoutException) {
|
||||
throw(TimeoutException) e;
|
||||
} else if(e instanceof StreamConnectFailedException) {
|
||||
throw(StreamConnectFailedException) e;
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String getCircuitTypeLabel();
|
||||
|
||||
public String toString() {
|
||||
return " Circuit ("+ getCircuitTypeLabel() + ") id="+ getCircuitId() +" state=" + status.getStateAsString() +" "+ pathToString();
|
||||
}
|
||||
|
||||
|
||||
protected String pathToString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
for(CircuitNode node: nodeList) {
|
||||
if(sb.length() > 1)
|
||||
sb.append(",");
|
||||
sb.append(node.toString());
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public List<Stream> getActiveStreams() {
|
||||
if(io == null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return io.getActiveStreams();
|
||||
}
|
||||
}
|
||||
|
||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
||||
if(io != null) {
|
||||
writer.println(toString());
|
||||
renderer.renderComponent(writer, flags, io);
|
||||
}
|
||||
}
|
||||
}
|
395
src/java/com/subgraph/orchid/circuits/CircuitManagerImpl.java
Normal file
395
src/java/com/subgraph/orchid/circuits/CircuitManagerImpl.java
Normal file
@ -0,0 +1,395 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitBuildHandler;
|
||||
import com.subgraph.orchid.CircuitManager;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.Connection;
|
||||
import com.subgraph.orchid.ConnectionCache;
|
||||
import com.subgraph.orchid.ConsensusDocument;
|
||||
import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.DirectoryCircuit;
|
||||
import com.subgraph.orchid.ExitCircuit;
|
||||
import com.subgraph.orchid.InternalCircuit;
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.guards.EntryGuards;
|
||||
import com.subgraph.orchid.circuits.hs.HiddenServiceManager;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
|
||||
|
||||
public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
|
||||
private final static int OPEN_DIRECTORY_STREAM_RETRY_COUNT = 5;
|
||||
private final static int OPEN_DIRECTORY_STREAM_TIMEOUT = 10 * 1000;
|
||||
|
||||
interface CircuitFilter {
|
||||
boolean filter(Circuit circuit);
|
||||
}
|
||||
|
||||
private final TorConfig config;
|
||||
private final Directory directory;
|
||||
private final ConnectionCache connectionCache;
|
||||
private final Set<CircuitImpl> activeCircuits;
|
||||
private final Queue<InternalCircuit> cleanInternalCircuits;
|
||||
private int requestedInternalCircuitCount = 0;
|
||||
private int pendingInternalCircuitCount = 0;
|
||||
private final TorRandom random;
|
||||
private final PendingExitStreams pendingExitStreams;
|
||||
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final CircuitCreationTask circuitCreationTask;
|
||||
private final TorInitializationTracker initializationTracker;
|
||||
private final CircuitPathChooser pathChooser;
|
||||
private final HiddenServiceManager hiddenServiceManager;
|
||||
|
||||
public CircuitManagerImpl(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker initializationTracker) {
|
||||
this.config = config;
|
||||
this.directory = directory;
|
||||
this.connectionCache = connectionCache;
|
||||
this.pathChooser = CircuitPathChooser.create(config, directory);
|
||||
if(config.getUseEntryGuards() || config.getUseBridges()) {
|
||||
this.pathChooser.enableEntryGuards(new EntryGuards(config, connectionCache, directoryDownloader, directory));
|
||||
}
|
||||
this.pendingExitStreams = new PendingExitStreams(config);
|
||||
this.circuitCreationTask = new CircuitCreationTask(config, directory, connectionCache, pathChooser, this, initializationTracker);
|
||||
this.activeCircuits = new HashSet<CircuitImpl>();
|
||||
this.cleanInternalCircuits = new LinkedList<InternalCircuit>();
|
||||
this.random = new TorRandom();
|
||||
|
||||
this.initializationTracker = initializationTracker;
|
||||
this.hiddenServiceManager = new HiddenServiceManager(config, directory, this);
|
||||
|
||||
directoryDownloader.setCircuitManager(this);
|
||||
}
|
||||
|
||||
public void startBuildingCircuits() {
|
||||
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public synchronized void stopBuildingCircuits(boolean killCircuits) {
|
||||
scheduledExecutor.shutdownNow();
|
||||
if(killCircuits) {
|
||||
List<CircuitImpl> circuits = new ArrayList<CircuitImpl>(activeCircuits);
|
||||
for(CircuitImpl c: circuits) {
|
||||
c.destroyCircuit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExitCircuit createNewExitCircuit(Router exitRouter) {
|
||||
return CircuitImpl.createExitCircuit(this, exitRouter);
|
||||
}
|
||||
|
||||
void addActiveCircuit(CircuitImpl circuit) {
|
||||
synchronized (activeCircuits) {
|
||||
activeCircuits.add(circuit);
|
||||
activeCircuits.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void removeActiveCircuit(CircuitImpl circuit) {
|
||||
synchronized (activeCircuits) {
|
||||
activeCircuits.remove(circuit);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized int getActiveCircuitCount() {
|
||||
return activeCircuits.size();
|
||||
}
|
||||
|
||||
Set<Circuit> getPendingCircuits() {
|
||||
return getCircuitsByFilter(new CircuitFilter() {
|
||||
public boolean filter(Circuit circuit) {
|
||||
return circuit.isPending();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
synchronized int getPendingCircuitCount() {
|
||||
return getPendingCircuits().size();
|
||||
}
|
||||
|
||||
Set<Circuit> getCircuitsByFilter(CircuitFilter filter) {
|
||||
final Set<Circuit> result = new HashSet<Circuit>();
|
||||
synchronized (activeCircuits) {
|
||||
for(CircuitImpl c: activeCircuits) {
|
||||
if(filter == null || filter.filter(c)) {
|
||||
result.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<ExitCircuit> getRandomlyOrderedListOfExitCircuits() {
|
||||
final Set<Circuit> notDirectory = getCircuitsByFilter(new CircuitFilter() {
|
||||
|
||||
public boolean filter(Circuit circuit) {
|
||||
final boolean exitType = circuit instanceof ExitCircuit;
|
||||
return exitType && !circuit.isMarkedForClose() && circuit.isConnected();
|
||||
}
|
||||
});
|
||||
final ArrayList<ExitCircuit> ac = new ArrayList<ExitCircuit>();
|
||||
for(Circuit c: notDirectory) {
|
||||
if(c instanceof ExitCircuit) {
|
||||
ac.add((ExitCircuit) c);
|
||||
}
|
||||
}
|
||||
final int sz = ac.size();
|
||||
for(int i = 0; i < sz; i++) {
|
||||
final ExitCircuit tmp = ac.get(i);
|
||||
final int swapIdx = random.nextInt(sz);
|
||||
ac.set(i, ac.get(swapIdx));
|
||||
ac.set(swapIdx, tmp);
|
||||
}
|
||||
return ac;
|
||||
}
|
||||
|
||||
public Stream openExitStreamTo(String hostname, int port)
|
||||
throws InterruptedException, TimeoutException, OpenFailedException {
|
||||
if(hostname.endsWith(".onion")) {
|
||||
return hiddenServiceManager.getStreamTo(hostname, port);
|
||||
}
|
||||
validateHostname(hostname);
|
||||
circuitCreationTask.predictPort(port);
|
||||
return pendingExitStreams.openExitStream(hostname, port);
|
||||
}
|
||||
|
||||
private void validateHostname(String hostname) throws OpenFailedException {
|
||||
maybeRejectInternalAddress(hostname);
|
||||
if(hostname.toLowerCase().endsWith(".onion")) {
|
||||
throw new OpenFailedException("Hidden services not supported");
|
||||
} else if(hostname.toLowerCase().endsWith(".exit")) {
|
||||
throw new OpenFailedException(".exit addresses are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeRejectInternalAddress(String hostname) throws OpenFailedException {
|
||||
if(IPv4Address.isValidIPv4AddressString(hostname)) {
|
||||
maybeRejectInternalAddress(IPv4Address.createFromString(hostname));
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeRejectInternalAddress(IPv4Address address) throws OpenFailedException {
|
||||
final InetAddress inetAddress = address.toInetAddress();
|
||||
if(inetAddress.isSiteLocalAddress() && config.getClientRejectInternalAddress()) {
|
||||
throw new OpenFailedException("Rejecting stream target with internal address: "+ address);
|
||||
}
|
||||
}
|
||||
public Stream openExitStreamTo(IPv4Address address, int port)
|
||||
throws InterruptedException, TimeoutException, OpenFailedException {
|
||||
maybeRejectInternalAddress(address);
|
||||
circuitCreationTask.predictPort(port);
|
||||
return pendingExitStreams.openExitStream(address, port);
|
||||
}
|
||||
|
||||
public List<StreamExitRequest> getPendingExitStreams() {
|
||||
return pendingExitStreams.getUnreservedPendingRequests();
|
||||
}
|
||||
|
||||
public Stream openDirectoryStream() throws OpenFailedException, InterruptedException, TimeoutException {
|
||||
return openDirectoryStream(0);
|
||||
}
|
||||
|
||||
public Stream openDirectoryStream(int purpose) throws OpenFailedException, InterruptedException {
|
||||
final int requestEventCode = purposeToEventCode(purpose, false);
|
||||
final int loadingEventCode = purposeToEventCode(purpose, true);
|
||||
|
||||
int failCount = 0;
|
||||
while(failCount < OPEN_DIRECTORY_STREAM_RETRY_COUNT) {
|
||||
final DirectoryCircuit circuit = openDirectoryCircuit();
|
||||
if(requestEventCode > 0) {
|
||||
initializationTracker.notifyEvent(requestEventCode);
|
||||
}
|
||||
try {
|
||||
final Stream stream = circuit.openDirectoryStream(OPEN_DIRECTORY_STREAM_TIMEOUT, true);
|
||||
if(loadingEventCode > 0) {
|
||||
initializationTracker.notifyEvent(loadingEventCode);
|
||||
}
|
||||
return stream;
|
||||
} catch (StreamConnectFailedException e) {
|
||||
circuit.markForClose();
|
||||
failCount += 1;
|
||||
} catch (TimeoutException e) {
|
||||
circuit.markForClose();
|
||||
}
|
||||
}
|
||||
throw new OpenFailedException("Retry count exceeded opening directory stream");
|
||||
}
|
||||
|
||||
public DirectoryCircuit openDirectoryCircuit() throws OpenFailedException {
|
||||
int failCount = 0;
|
||||
while(failCount < OPEN_DIRECTORY_STREAM_RETRY_COUNT) {
|
||||
final DirectoryCircuit circuit = CircuitImpl.createDirectoryCircuit(this);
|
||||
if(tryOpenCircuit(circuit, true, true)) {
|
||||
return circuit;
|
||||
}
|
||||
failCount += 1;
|
||||
}
|
||||
throw new OpenFailedException("Could not create circuit for directory stream");
|
||||
}
|
||||
|
||||
private int purposeToEventCode(int purpose, boolean getLoadingEvent) {
|
||||
switch(purpose) {
|
||||
case DIRECTORY_PURPOSE_CONSENSUS:
|
||||
return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_STATUS : Tor.BOOTSTRAP_STATUS_REQUESTING_STATUS;
|
||||
case DIRECTORY_PURPOSE_CERTIFICATES:
|
||||
return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_KEYS : Tor.BOOTSTRAP_STATUS_REQUESTING_KEYS;
|
||||
case DIRECTORY_PURPOSE_DESCRIPTORS:
|
||||
return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_DESCRIPTORS : Tor.BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DirectoryCircuitResult implements CircuitBuildHandler {
|
||||
|
||||
private boolean isFailed;
|
||||
|
||||
public void connectionCompleted(Connection connection) {}
|
||||
public void nodeAdded(CircuitNode node) {}
|
||||
public void circuitBuildCompleted(Circuit circuit) {}
|
||||
|
||||
public void connectionFailed(String reason) {
|
||||
isFailed = true;
|
||||
}
|
||||
|
||||
public void circuitBuildFailed(String reason) {
|
||||
isFailed = true;
|
||||
}
|
||||
|
||||
boolean isSuccessful() {
|
||||
return !isFailed;
|
||||
}
|
||||
}
|
||||
|
||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
|
||||
if((flags & DASHBOARD_CIRCUITS) == 0) {
|
||||
return;
|
||||
}
|
||||
renderer.renderComponent(writer, flags, connectionCache);
|
||||
renderer.renderComponent(writer, flags, circuitCreationTask.getCircuitPredictor());
|
||||
writer.println("[Circuit Manager]");
|
||||
writer.println();
|
||||
for(Circuit c: getCircuitsByFilter(null)) {
|
||||
renderer.renderComponent(writer, flags, c);
|
||||
}
|
||||
}
|
||||
|
||||
public InternalCircuit getCleanInternalCircuit() throws InterruptedException {
|
||||
synchronized(cleanInternalCircuits) {
|
||||
try {
|
||||
requestedInternalCircuitCount += 1;
|
||||
while(cleanInternalCircuits.isEmpty()) {
|
||||
cleanInternalCircuits.wait();
|
||||
}
|
||||
return cleanInternalCircuits.remove();
|
||||
} finally {
|
||||
requestedInternalCircuitCount -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getNeededCleanCircuitCount(boolean isPredicted) {
|
||||
synchronized (cleanInternalCircuits) {
|
||||
final int predictedCount = (isPredicted) ? 2 : 0;
|
||||
final int needed = Math.max(requestedInternalCircuitCount, predictedCount) - (pendingInternalCircuitCount + cleanInternalCircuits.size());
|
||||
if(needed < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return needed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void incrementPendingInternalCircuitCount() {
|
||||
synchronized (cleanInternalCircuits) {
|
||||
pendingInternalCircuitCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void decrementPendingInternalCircuitCount() {
|
||||
synchronized (cleanInternalCircuits) {
|
||||
pendingInternalCircuitCount -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void addCleanInternalCircuit(InternalCircuit circuit) {
|
||||
synchronized(cleanInternalCircuits) {
|
||||
pendingInternalCircuitCount -= 1;
|
||||
cleanInternalCircuits.add(circuit);
|
||||
cleanInternalCircuits.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isNtorEnabled() {
|
||||
switch(config.getUseNTorHandshake()) {
|
||||
case AUTO:
|
||||
return isNtorEnabledInConsensus();
|
||||
case FALSE:
|
||||
return false;
|
||||
case TRUE:
|
||||
return true;
|
||||
default:
|
||||
throw new IllegalArgumentException("getUseNTorHandshake() returned "+ config.getUseNTorHandshake());
|
||||
}
|
||||
}
|
||||
|
||||
boolean isNtorEnabledInConsensus() {
|
||||
ConsensusDocument consensus = directory.getCurrentConsensusDocument();
|
||||
return (consensus != null) && (consensus.getUseNTorHandshake());
|
||||
}
|
||||
|
||||
public DirectoryCircuit openDirectoryCircuitTo(List<Router> path) throws OpenFailedException {
|
||||
final DirectoryCircuit circuit = CircuitImpl.createDirectoryCircuitTo(this, path);
|
||||
if(!tryOpenCircuit(circuit, true, false)) {
|
||||
throw new OpenFailedException("Could not create directory circuit for path");
|
||||
}
|
||||
return circuit;
|
||||
}
|
||||
|
||||
public ExitCircuit openExitCircuitTo(List<Router> path) throws OpenFailedException {
|
||||
final ExitCircuit circuit = CircuitImpl.createExitCircuitTo(this, path);
|
||||
if(!tryOpenCircuit(circuit, false, false)) {
|
||||
throw new OpenFailedException("Could not create exit circuit for path");
|
||||
}
|
||||
return circuit;
|
||||
}
|
||||
|
||||
public InternalCircuit openInternalCircuitTo(List<Router> path) throws OpenFailedException {
|
||||
final InternalCircuit circuit = CircuitImpl.createInternalCircuitTo(this, path);
|
||||
if(!tryOpenCircuit(circuit, false, false)) {
|
||||
throw new OpenFailedException("Could not create internal circuit for path");
|
||||
}
|
||||
return circuit;
|
||||
}
|
||||
|
||||
private boolean tryOpenCircuit(Circuit circuit, boolean isDirectory, boolean trackInitialization) {
|
||||
final DirectoryCircuitResult result = new DirectoryCircuitResult();
|
||||
final CircuitCreationRequest req = new CircuitCreationRequest(pathChooser, circuit, result, isDirectory);
|
||||
final CircuitBuildTask task = new CircuitBuildTask(req, connectionCache, isNtorEnabled(), (trackInitialization) ? (initializationTracker) : (null));
|
||||
task.run();
|
||||
return result.isSuccessful();
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
||||
import com.subgraph.orchid.crypto.TorStreamCipher;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
|
||||
public class CircuitNodeCryptoState {
|
||||
public final static int KEY_MATERIAL_SIZE = TorMessageDigest.TOR_DIGEST_SIZE * 2 + TorStreamCipher.KEY_LEN * 2;
|
||||
|
||||
public static CircuitNodeCryptoState createFromKeyMaterial(byte[] keyMaterial, byte[] verifyDigest) {
|
||||
return new CircuitNodeCryptoState(keyMaterial, verifyDigest);
|
||||
}
|
||||
|
||||
private final HexDigest checksumDigest;
|
||||
private final TorMessageDigest forwardDigest;
|
||||
private final TorMessageDigest backwardDigest;
|
||||
private final TorStreamCipher forwardCipher;
|
||||
private final TorStreamCipher backwardCipher;
|
||||
|
||||
static private byte[] extractDigestBytes(byte[] keyMaterial, int offset) {
|
||||
final byte[] digestBytes = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
||||
System.arraycopy(keyMaterial, offset, digestBytes, 0, TorMessageDigest.TOR_DIGEST_SIZE);
|
||||
return digestBytes;
|
||||
}
|
||||
|
||||
static private byte[] extractCipherKey(byte[] keyMaterial, int offset) {
|
||||
final byte[] keyBytes = new byte[TorStreamCipher.KEY_LEN];
|
||||
System.arraycopy(keyMaterial, offset, keyBytes, 0, TorStreamCipher.KEY_LEN);
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
private CircuitNodeCryptoState(byte[] keyMaterial, byte[] verifyDigest) {
|
||||
checksumDigest = HexDigest.createFromDigestBytes(verifyDigest);
|
||||
int offset = 0;
|
||||
|
||||
forwardDigest = new TorMessageDigest();
|
||||
forwardDigest.update(extractDigestBytes(keyMaterial, offset));
|
||||
offset += TorMessageDigest.TOR_DIGEST_SIZE;
|
||||
|
||||
backwardDigest = new TorMessageDigest();
|
||||
backwardDigest.update(extractDigestBytes(keyMaterial, offset));
|
||||
offset += TorMessageDigest.TOR_DIGEST_SIZE;
|
||||
|
||||
forwardCipher = TorStreamCipher.createFromKeyBytes(extractCipherKey(keyMaterial, offset));
|
||||
offset += TorStreamCipher.KEY_LEN;
|
||||
|
||||
backwardCipher = TorStreamCipher.createFromKeyBytes(extractCipherKey(keyMaterial, offset));
|
||||
}
|
||||
|
||||
boolean verifyPacketDigest(HexDigest packetDigest) {
|
||||
return checksumDigest.equals(packetDigest);
|
||||
}
|
||||
|
||||
void encryptForwardCell(Cell cell) {
|
||||
forwardCipher.encrypt(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
||||
}
|
||||
|
||||
boolean decryptBackwardCell(Cell cell) {
|
||||
backwardCipher.encrypt(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
||||
return isRecognizedCell(cell);
|
||||
}
|
||||
|
||||
void updateForwardDigest(Cell cell) {
|
||||
forwardDigest.update(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
||||
}
|
||||
|
||||
byte[] getForwardDigestBytes() {
|
||||
return forwardDigest.getDigestBytes();
|
||||
}
|
||||
|
||||
private boolean isRecognizedCell(Cell cell) {
|
||||
if(cell.getShortAt(RelayCell.RECOGNIZED_OFFSET) != 0)
|
||||
return false;
|
||||
|
||||
final byte[] digest = extractRelayDigest(cell);
|
||||
final byte[] peek = backwardDigest.peekDigest(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
||||
for(int i = 0; i < 4; i++)
|
||||
if(digest[i] != peek[i]) {
|
||||
replaceRelayDigest(cell, digest);
|
||||
return false;
|
||||
}
|
||||
backwardDigest.update(cell.getCellBytes(), Cell.CELL_HEADER_LEN, Cell.CELL_PAYLOAD_LEN);
|
||||
replaceRelayDigest(cell, digest);
|
||||
return true;
|
||||
}
|
||||
|
||||
private byte[] extractRelayDigest(Cell cell) {
|
||||
final byte[] digest = new byte[4];
|
||||
for(int i = 0; i < 4; i++) {
|
||||
digest[i] = (byte) cell.getByteAt(i + RelayCell.DIGEST_OFFSET);
|
||||
cell.putByteAt(i + RelayCell.DIGEST_OFFSET, 0);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
private void replaceRelayDigest(Cell cell, byte[] digest) {
|
||||
for(int i = 0; i < 4; i++)
|
||||
cell.putByteAt(i + RelayCell.DIGEST_OFFSET, digest[i] & 0xFF);
|
||||
}
|
||||
}
|
121
src/java/com/subgraph/orchid/circuits/CircuitNodeImpl.java
Normal file
121
src/java/com/subgraph/orchid/circuits/CircuitNodeImpl.java
Normal file
@ -0,0 +1,121 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.TorException;
|
||||
|
||||
public class CircuitNodeImpl implements CircuitNode {
|
||||
|
||||
public static CircuitNode createAnonymous(CircuitNode previous, byte[] keyMaterial, byte[] verifyDigest) {
|
||||
return createNode(null, previous, keyMaterial, verifyDigest);
|
||||
}
|
||||
|
||||
public static CircuitNode createFirstHop(Router r, byte[] keyMaterial, byte[] verifyDigest) {
|
||||
return createNode(r, null, keyMaterial, verifyDigest);
|
||||
}
|
||||
|
||||
public static CircuitNode createNode(Router r, CircuitNode previous, byte[] keyMaterial, byte[] verifyDigest) {
|
||||
final CircuitNodeCryptoState cs = CircuitNodeCryptoState.createFromKeyMaterial(keyMaterial, verifyDigest);
|
||||
return new CircuitNodeImpl(r, previous, cs);
|
||||
}
|
||||
|
||||
private final static int CIRCWINDOW_START = 1000;
|
||||
private final static int CIRCWINDOW_INCREMENT = 100;
|
||||
|
||||
private final Router router;
|
||||
private final CircuitNodeCryptoState cryptoState;
|
||||
private final CircuitNode previousNode;
|
||||
|
||||
private final Object windowLock;
|
||||
private int packageWindow;
|
||||
private int deliverWindow;
|
||||
|
||||
private CircuitNodeImpl(Router router, CircuitNode previous, CircuitNodeCryptoState cryptoState) {
|
||||
previousNode = previous;
|
||||
this.router = router;
|
||||
this.cryptoState = cryptoState;
|
||||
windowLock = new Object();
|
||||
packageWindow = CIRCWINDOW_START;
|
||||
deliverWindow = CIRCWINDOW_START;
|
||||
}
|
||||
|
||||
public Router getRouter() {
|
||||
return router;
|
||||
}
|
||||
|
||||
public CircuitNode getPreviousNode() {
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
public void encryptForwardCell(RelayCell cell) {
|
||||
cryptoState.encryptForwardCell(cell);
|
||||
}
|
||||
|
||||
public boolean decryptBackwardCell(Cell cell) {
|
||||
return cryptoState.decryptBackwardCell(cell);
|
||||
}
|
||||
|
||||
public void updateForwardDigest(RelayCell cell) {
|
||||
cryptoState.updateForwardDigest(cell);
|
||||
}
|
||||
|
||||
public byte[] getForwardDigestBytes() {
|
||||
return cryptoState.getForwardDigestBytes();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if(router != null) {
|
||||
return "|"+ router.getNickname() + "|";
|
||||
} else {
|
||||
return "|()|";
|
||||
}
|
||||
}
|
||||
|
||||
public void decrementDeliverWindow() {
|
||||
synchronized(windowLock) {
|
||||
deliverWindow--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean considerSendingSendme() {
|
||||
synchronized(windowLock) {
|
||||
if(deliverWindow <= (CIRCWINDOW_START - CIRCWINDOW_INCREMENT)) {
|
||||
deliverWindow += CIRCWINDOW_INCREMENT;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForSendWindow() {
|
||||
waitForSendWindow(false);
|
||||
}
|
||||
|
||||
public void waitForSendWindowAndDecrement() {
|
||||
waitForSendWindow(true);
|
||||
}
|
||||
|
||||
private void waitForSendWindow(boolean decrement) {
|
||||
synchronized(windowLock) {
|
||||
while(packageWindow == 0) {
|
||||
try {
|
||||
windowLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new TorException("Thread interrupted while waiting for circuit send window");
|
||||
}
|
||||
}
|
||||
if(decrement)
|
||||
packageWindow--;
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementSendWindow() {
|
||||
synchronized(windowLock) {
|
||||
packageWindow += CIRCWINDOW_INCREMENT;
|
||||
windowLock.notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
99
src/java/com/subgraph/orchid/circuits/CircuitPredictor.java
Normal file
99
src/java/com/subgraph/orchid/circuits/CircuitPredictor.java
Normal file
@ -0,0 +1,99 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
||||
|
||||
public class CircuitPredictor implements DashboardRenderable {
|
||||
|
||||
private final static Integer INTERNAL_CIRCUIT_PORT_VALUE = 0;
|
||||
private final static long TIMEOUT_MS = 60 * 60 * 1000; // One hour
|
||||
|
||||
private final Map<Integer, Long> portsSeen;
|
||||
|
||||
public CircuitPredictor() {
|
||||
portsSeen = new HashMap<Integer,Long>();
|
||||
addExitPortRequest(80);
|
||||
addInternalRequest();
|
||||
}
|
||||
|
||||
void addExitPortRequest(int port) {
|
||||
synchronized (portsSeen) {
|
||||
portsSeen.put(port, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
void addInternalRequest() {
|
||||
addExitPortRequest(INTERNAL_CIRCUIT_PORT_VALUE);
|
||||
}
|
||||
|
||||
|
||||
private boolean isEntryExpired(Entry<Integer, Long> e, long now) {
|
||||
return (now - e.getValue()) > TIMEOUT_MS;
|
||||
}
|
||||
|
||||
private void removeExpiredPorts() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final Iterator<Entry<Integer, Long>> it = portsSeen.entrySet().iterator();
|
||||
while(it.hasNext()) {
|
||||
if(isEntryExpired(it.next(), now)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isInternalPredicted() {
|
||||
synchronized (portsSeen) {
|
||||
removeExpiredPorts();
|
||||
return portsSeen.containsKey(INTERNAL_CIRCUIT_PORT_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Integer> getPredictedPorts() {
|
||||
synchronized (portsSeen) {
|
||||
removeExpiredPorts();
|
||||
final Set<Integer> result = new HashSet<Integer>(portsSeen.keySet());
|
||||
result.remove(INTERNAL_CIRCUIT_PORT_VALUE);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
List<PredictedPortTarget> getPredictedPortTargets() {
|
||||
final List<PredictedPortTarget> targets = new ArrayList<PredictedPortTarget>();
|
||||
for(int p: getPredictedPorts()) {
|
||||
targets.add(new PredictedPortTarget(p));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags)
|
||||
throws IOException {
|
||||
|
||||
if((flags & DASHBOARD_PREDICTED_PORTS) == 0) {
|
||||
return;
|
||||
}
|
||||
writer.println("[Predicted Ports] ");
|
||||
for(int port : portsSeen.keySet()) {
|
||||
writer.write(" "+ port);
|
||||
Long lastSeen = portsSeen.get(port);
|
||||
if(lastSeen != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
long ms = now - lastSeen;
|
||||
writer.write(" (last seen "+ TimeUnit.MINUTES.convert(ms, TimeUnit.MILLISECONDS) +" minutes ago)");
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
}
|
115
src/java/com/subgraph/orchid/circuits/CircuitStatus.java
Normal file
115
src/java/com/subgraph/orchid/circuits/CircuitStatus.java
Normal file
@ -0,0 +1,115 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
|
||||
public class CircuitStatus {
|
||||
|
||||
enum CircuitState {
|
||||
UNCONNECTED("Unconnected"),
|
||||
BUILDING("Building"),
|
||||
FAILED("Failed"),
|
||||
OPEN("Open"),
|
||||
DESTROYED("Destroyed");
|
||||
String name;
|
||||
CircuitState(String name) { this.name = name; }
|
||||
public String toString() { return name; }
|
||||
}
|
||||
|
||||
private long timestampCreated;
|
||||
private long timestampDirty;
|
||||
private int currentStreamId;
|
||||
private Object streamIdLock = new Object();
|
||||
private volatile CircuitState state = CircuitState.UNCONNECTED;
|
||||
|
||||
CircuitStatus() {
|
||||
initializeCurrentStreamId();
|
||||
}
|
||||
|
||||
private void initializeCurrentStreamId() {
|
||||
final TorRandom random = new TorRandom();
|
||||
currentStreamId = random.nextInt(0xFFFF) + 1;
|
||||
}
|
||||
|
||||
synchronized void updateCreatedTimestamp() {
|
||||
timestampCreated = System.currentTimeMillis();
|
||||
timestampDirty = 0;
|
||||
}
|
||||
|
||||
synchronized void updateDirtyTimestamp() {
|
||||
if(timestampDirty == 0 && state != CircuitState.BUILDING) {
|
||||
timestampDirty = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized long getMillisecondsElapsedSinceCreated() {
|
||||
return millisecondsElapsedSince(timestampCreated);
|
||||
}
|
||||
|
||||
synchronized long getMillisecondsDirty() {
|
||||
return millisecondsElapsedSince(timestampDirty);
|
||||
}
|
||||
|
||||
private static long millisecondsElapsedSince(long then) {
|
||||
if(then == 0) {
|
||||
return 0;
|
||||
}
|
||||
final long now = System.currentTimeMillis();
|
||||
return now - then;
|
||||
}
|
||||
|
||||
synchronized boolean isDirty() {
|
||||
return timestampDirty != 0;
|
||||
}
|
||||
|
||||
void setStateBuilding() {
|
||||
state = CircuitState.BUILDING;
|
||||
}
|
||||
|
||||
void setStateFailed() {
|
||||
state = CircuitState.FAILED;
|
||||
}
|
||||
|
||||
void setStateOpen() {
|
||||
state = CircuitState.OPEN;
|
||||
}
|
||||
|
||||
void setStateDestroyed() {
|
||||
state = CircuitState.DESTROYED;
|
||||
}
|
||||
|
||||
boolean isBuilding() {
|
||||
return state == CircuitState.BUILDING;
|
||||
}
|
||||
|
||||
boolean isConnected() {
|
||||
return state == CircuitState.OPEN;
|
||||
}
|
||||
|
||||
boolean isUnconnected() {
|
||||
return state == CircuitState.UNCONNECTED;
|
||||
}
|
||||
|
||||
String getStateAsString() {
|
||||
if(state == CircuitState.OPEN) {
|
||||
return state.toString() + " ["+ getDirtyString() + "]";
|
||||
}
|
||||
return state.toString();
|
||||
}
|
||||
|
||||
private String getDirtyString() {
|
||||
if(!isDirty()) {
|
||||
return "Clean";
|
||||
} else {
|
||||
return "Dirty "+ (getMillisecondsDirty() / 1000) +"s";
|
||||
}
|
||||
}
|
||||
int nextStreamId() {
|
||||
synchronized(streamIdLock) {
|
||||
currentStreamId++;
|
||||
if(currentStreamId > 0xFFFF)
|
||||
currentStreamId = 1;
|
||||
return currentStreamId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.DirectoryCircuit;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
||||
|
||||
public class DirectoryCircuitImpl extends CircuitImpl implements DirectoryCircuit {
|
||||
|
||||
protected DirectoryCircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
super(circuitManager, prechosenPath);
|
||||
}
|
||||
|
||||
public Stream openDirectoryStream(long timeout, boolean autoclose) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
final StreamImpl stream = createNewStream(autoclose);
|
||||
try {
|
||||
stream.openDirectory(timeout);
|
||||
return stream;
|
||||
} catch (Exception e) {
|
||||
removeStream(stream);
|
||||
return processStreamOpenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
|
||||
if(prechosenPath != null) {
|
||||
return prechosenPath;
|
||||
}
|
||||
return pathChooser.chooseDirectoryPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCircuitTypeLabel() {
|
||||
return "Directory";
|
||||
}
|
||||
}
|
87
src/java/com/subgraph/orchid/circuits/ExitCircuitImpl.java
Normal file
87
src/java/com/subgraph/orchid/circuits/ExitCircuitImpl.java
Normal file
@ -0,0 +1,87 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.ExitCircuit;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
||||
|
||||
public class ExitCircuitImpl extends CircuitImpl implements ExitCircuit {
|
||||
|
||||
private final Router exitRouter;
|
||||
private final Set<ExitTarget> failedExitRequests;
|
||||
|
||||
ExitCircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
super(circuitManager, prechosenPath);
|
||||
this.exitRouter = prechosenPath.get(prechosenPath.size() - 1);
|
||||
this.failedExitRequests = new HashSet<ExitTarget>();
|
||||
}
|
||||
|
||||
ExitCircuitImpl(CircuitManagerImpl circuitManager, Router exitRouter) {
|
||||
super(circuitManager);
|
||||
this.exitRouter = exitRouter;
|
||||
this.failedExitRequests = new HashSet<ExitTarget>();
|
||||
}
|
||||
|
||||
public Stream openExitStream(IPv4Address address, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
return openExitStream(address.toString(), port, timeout);
|
||||
}
|
||||
|
||||
public Stream openExitStream(String target, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
final StreamImpl stream = createNewStream();
|
||||
try {
|
||||
stream.openExit(target, port, timeout);
|
||||
return stream;
|
||||
} catch (Exception e) {
|
||||
removeStream(stream);
|
||||
return processStreamOpenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordFailedExitTarget(ExitTarget target) {
|
||||
synchronized(failedExitRequests) {
|
||||
failedExitRequests.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canHandleExitTo(ExitTarget target) {
|
||||
synchronized(failedExitRequests) {
|
||||
if(failedExitRequests.contains(target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(isMarkedForClose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(target.isAddressTarget()) {
|
||||
return exitRouter.exitPolicyAccepts(target.getAddress(), target.getPort());
|
||||
} else {
|
||||
return exitRouter.exitPolicyAccepts(target.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canHandleExitToPort(int port) {
|
||||
return exitRouter.exitPolicyAccepts(port);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
|
||||
return pathChooser.choosePathWithExit(exitRouter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCircuitTypeLabel() {
|
||||
return "Exit";
|
||||
}
|
||||
}
|
118
src/java/com/subgraph/orchid/circuits/InternalCircuitImpl.java
Normal file
118
src/java/com/subgraph/orchid/circuits/InternalCircuitImpl.java
Normal file
@ -0,0 +1,118 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.DirectoryCircuit;
|
||||
import com.subgraph.orchid.HiddenServiceCircuit;
|
||||
import com.subgraph.orchid.InternalCircuit;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
|
||||
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
|
||||
|
||||
public class InternalCircuitImpl extends CircuitImpl implements InternalCircuit, DirectoryCircuit, HiddenServiceCircuit {
|
||||
|
||||
private enum InternalType { UNUSED, HS_INTRODUCTION, HS_DIRECTORY, HS_CIRCUIT }
|
||||
|
||||
private InternalType type;
|
||||
private boolean ntorEnabled;
|
||||
|
||||
InternalCircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
|
||||
super(circuitManager, prechosenPath);
|
||||
this.type = InternalType.UNUSED;
|
||||
this.ntorEnabled = circuitManager.isNtorEnabled();
|
||||
}
|
||||
|
||||
protected InternalCircuitImpl(CircuitManagerImpl circuitManager) {
|
||||
this(circuitManager, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Router> choosePathForCircuit(CircuitPathChooser pathChooser)
|
||||
throws InterruptedException, PathSelectionFailedException {
|
||||
return pathChooser.chooseInternalPath();
|
||||
}
|
||||
|
||||
|
||||
public Circuit cannibalizeToIntroductionPoint(Router target) {
|
||||
cannibalizeTo(target);
|
||||
type = InternalType.HS_INTRODUCTION;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void cannibalizeTo(Router target) {
|
||||
if(type != InternalType.UNUSED) {
|
||||
throw new IllegalStateException("Cannot cannibalize internal circuit with type "+ type);
|
||||
|
||||
}
|
||||
final CircuitExtender extender = new CircuitExtender(this, ntorEnabled);
|
||||
extender.extendTo(target);
|
||||
}
|
||||
|
||||
public Stream openDirectoryStream(long timeout, boolean autoclose) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
if(type != InternalType.HS_DIRECTORY) {
|
||||
throw new IllegalStateException("Cannot open directory stream on internal circuit with type "+ type);
|
||||
}
|
||||
final StreamImpl stream = createNewStream();
|
||||
try {
|
||||
stream.openDirectory(timeout);
|
||||
return stream;
|
||||
} catch (Exception e) {
|
||||
removeStream(stream);
|
||||
return processStreamOpenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public DirectoryCircuit cannibalizeToDirectory(Router target) {
|
||||
cannibalizeTo(target);
|
||||
type = InternalType.HS_DIRECTORY;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public HiddenServiceCircuit connectHiddenService(CircuitNode node) {
|
||||
if(type != InternalType.UNUSED) {
|
||||
throw new IllegalStateException("Cannot connect hidden service from internal circuit type "+ type);
|
||||
}
|
||||
appendNode(node);
|
||||
type = InternalType.HS_CIRCUIT;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Stream openStream(int port, long timeout)
|
||||
throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
if(type != InternalType.HS_CIRCUIT) {
|
||||
throw new IllegalStateException("Cannot open stream to hidden service from internal circuit type "+ type);
|
||||
}
|
||||
final StreamImpl stream = createNewStream();
|
||||
try {
|
||||
stream.openExit("", port, timeout);
|
||||
return stream;
|
||||
} catch (Exception e) {
|
||||
removeStream(stream);
|
||||
return processStreamOpenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getCircuitTypeLabel() {
|
||||
switch(type) {
|
||||
case HS_CIRCUIT:
|
||||
return "Hidden Service";
|
||||
case HS_DIRECTORY:
|
||||
return "HS Directory";
|
||||
case HS_INTRODUCTION:
|
||||
return "HS Introduction";
|
||||
case UNUSED:
|
||||
return "Internal";
|
||||
default:
|
||||
return "(null)";
|
||||
}
|
||||
}
|
||||
}
|
114
src/java/com/subgraph/orchid/circuits/NTorCircuitExtender.java
Normal file
114
src/java/com/subgraph/orchid/circuits/NTorCircuitExtender.java
Normal file
@ -0,0 +1,114 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
||||
import com.subgraph.orchid.crypto.TorNTorKeyAgreement;
|
||||
|
||||
public class NTorCircuitExtender {
|
||||
private final static Logger logger = Logger.getLogger(NTorCircuitExtender.class.getName());
|
||||
|
||||
private final CircuitExtender extender;
|
||||
private final Router router;
|
||||
private final TorNTorKeyAgreement kex;
|
||||
|
||||
public NTorCircuitExtender(CircuitExtender extender, Router router) {
|
||||
this.extender = extender;
|
||||
this.router = router;
|
||||
this.kex = new TorNTorKeyAgreement(router.getIdentityHash(), router.getNTorOnionKey());
|
||||
}
|
||||
|
||||
CircuitNode extendTo() {
|
||||
final byte[] onion = kex.createOnionSkin();
|
||||
if(finalRouterSupportsExtend2()) {
|
||||
logger.fine("Extending circuit to "+ router.getNickname() + " with NTor inside RELAY_EXTEND2");
|
||||
return extendWithExtend2(onion);
|
||||
} else {
|
||||
logger.fine("Extending circuit to "+ router.getNickname() + " with NTor inside RELAY_EXTEND");
|
||||
return extendWithTunneledExtend(onion);
|
||||
}
|
||||
}
|
||||
|
||||
private CircuitNode extendWithExtend2(byte[] onion) {
|
||||
final RelayCell cell = createExtend2Cell(onion);
|
||||
extender.sendRelayCell(cell);
|
||||
final RelayCell response = extender.receiveRelayResponse(RelayCell.RELAY_EXTENDED2, router);
|
||||
return processExtended2(response);
|
||||
}
|
||||
|
||||
private CircuitNode extendWithTunneledExtend(byte[] onion) {
|
||||
final RelayCell cell = createExtendCell(onion, kex.getNtorCreateMagic());
|
||||
extender.sendRelayCell(cell);
|
||||
final RelayCell response = extender.receiveRelayResponse(RelayCell.RELAY_EXTENDED, router);
|
||||
return processExtended(response);
|
||||
}
|
||||
|
||||
private boolean finalRouterSupportsExtend2() {
|
||||
return extender.getFinalRouter().getNTorOnionKey() != null;
|
||||
}
|
||||
|
||||
private RelayCell createExtend2Cell(byte[] ntorOnionskin) {
|
||||
final RelayCell cell = extender.createRelayCell(RelayCell.RELAY_EXTEND2);
|
||||
|
||||
cell.putByte(2);
|
||||
|
||||
cell.putByte(0);
|
||||
cell.putByte(6);
|
||||
cell.putByteArray(router.getAddress().getAddressDataBytes());
|
||||
cell.putShort(router.getOnionPort());
|
||||
|
||||
cell.putByte(2);
|
||||
cell.putByte(20);
|
||||
cell.putByteArray(router.getIdentityHash().getRawBytes());
|
||||
|
||||
cell.putShort(0x0002);
|
||||
cell.putShort(ntorOnionskin.length);
|
||||
cell.putByteArray(ntorOnionskin);
|
||||
return cell;
|
||||
}
|
||||
|
||||
private RelayCell createExtendCell(byte[] ntorOnionskin, byte[] ntorMagic) {
|
||||
final RelayCell cell = extender.createRelayCell(RelayCell.RELAY_EXTEND);
|
||||
cell.putByteArray(router.getAddress().getAddressDataBytes());
|
||||
cell.putShort(router.getOnionPort());
|
||||
final int paddingLength = CircuitExtender.TAP_ONIONSKIN_LEN - (ntorOnionskin.length + ntorMagic.length);
|
||||
final byte[] padding = new byte[paddingLength];
|
||||
cell.putByteArray(ntorMagic);
|
||||
cell.putByteArray(ntorOnionskin);
|
||||
cell.putByteArray(padding);
|
||||
cell.putByteArray(router.getIdentityHash().getRawBytes());
|
||||
return cell;
|
||||
}
|
||||
|
||||
private CircuitNode processExtended(RelayCell cell) {
|
||||
byte[] payload = new byte[CircuitExtender.TAP_ONIONSKIN_REPLY_LEN];
|
||||
cell.getByteArray(payload);
|
||||
|
||||
return processPayload(payload);
|
||||
}
|
||||
|
||||
|
||||
private CircuitNode processExtended2(RelayCell cell) {
|
||||
final int payloadLength = cell.getShort();
|
||||
if(payloadLength > cell.cellBytesRemaining()) {
|
||||
throw new TorException("Incorrect payload length value in RELAY_EXTENED2 cell");
|
||||
}
|
||||
byte[] payload = new byte[payloadLength];
|
||||
cell.getByteArray(payload);
|
||||
|
||||
return processPayload(payload);
|
||||
}
|
||||
|
||||
private CircuitNode processPayload(byte[] payload) {
|
||||
final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE];
|
||||
final byte[] verifyDigest = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
||||
if(!kex.deriveKeysFromHandshakeResponse(payload, keyMaterial, verifyDigest)) {
|
||||
return null;
|
||||
}
|
||||
return extender.createNewNode(router, keyMaterial, verifyDigest);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.ExitCircuit;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
|
||||
public class OpenExitStreamTask implements Runnable {
|
||||
private final static Logger logger = Logger.getLogger(OpenExitStreamTask.class.getName());
|
||||
private final ExitCircuit circuit;
|
||||
private final StreamExitRequest exitRequest;
|
||||
|
||||
OpenExitStreamTask(ExitCircuit circuit, StreamExitRequest exitRequest) {
|
||||
this.circuit = circuit;
|
||||
this.exitRequest = exitRequest;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
logger.fine("Attempting to open stream to "+ exitRequest);
|
||||
try {
|
||||
exitRequest.setCompletedSuccessfully(tryOpenExitStream());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
exitRequest.setInterrupted();
|
||||
} catch (TimeoutException e) {
|
||||
circuit.markForClose();
|
||||
exitRequest.setCompletedTimeout();
|
||||
} catch (StreamConnectFailedException e) {
|
||||
if(!e.isReasonRetryable()) {
|
||||
exitRequest.setExitFailed();
|
||||
circuit.recordFailedExitTarget(exitRequest);
|
||||
} else {
|
||||
circuit.markForClose();
|
||||
exitRequest.setStreamOpenFailure(e.getReason());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Stream tryOpenExitStream() throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
if(exitRequest.isAddressTarget()) {
|
||||
return circuit.openExitStream(exitRequest.getAddress(), exitRequest.getPort(), exitRequest.getStreamTimeout());
|
||||
} else {
|
||||
return circuit.openExitStream(exitRequest.getHostname(), exitRequest.getPort(), exitRequest.getStreamTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
public class PendingExitStreams {
|
||||
|
||||
private final Set<StreamExitRequest> pendingRequests;
|
||||
private final Object lock = new Object();
|
||||
private final TorConfig config;
|
||||
|
||||
PendingExitStreams(TorConfig config) {
|
||||
this.config = config;
|
||||
pendingRequests = new HashSet<StreamExitRequest>();
|
||||
}
|
||||
|
||||
Stream openExitStream(IPv4Address address, int port) throws InterruptedException, OpenFailedException {
|
||||
final StreamExitRequest request = new StreamExitRequest(lock, address, port);
|
||||
return openExitStreamByRequest(request);
|
||||
}
|
||||
|
||||
Stream openExitStream(String hostname, int port) throws InterruptedException, OpenFailedException {
|
||||
final StreamExitRequest request = new StreamExitRequest(lock, hostname, port);
|
||||
return openExitStreamByRequest(request);
|
||||
}
|
||||
|
||||
private Stream openExitStreamByRequest(StreamExitRequest request) throws InterruptedException, OpenFailedException {
|
||||
if(config.getCircuitStreamTimeout() != 0) {
|
||||
request.setStreamTimeout(config.getCircuitStreamTimeout());
|
||||
}
|
||||
|
||||
synchronized(lock) {
|
||||
pendingRequests.add(request);
|
||||
try {
|
||||
return handleRequest(request);
|
||||
} finally {
|
||||
pendingRequests.remove(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Stream handleRequest(StreamExitRequest request) throws InterruptedException, OpenFailedException {
|
||||
while(true) {
|
||||
while(!request.isCompleted()) {
|
||||
lock.wait();
|
||||
}
|
||||
try {
|
||||
return request.getStream();
|
||||
} catch (TimeoutException e) {
|
||||
request.resetForRetry();
|
||||
} catch (StreamConnectFailedException e) {
|
||||
request.resetForRetry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<StreamExitRequest> getUnreservedPendingRequests() {
|
||||
final List<StreamExitRequest> result = new ArrayList<StreamExitRequest>();
|
||||
synchronized (lock) {
|
||||
for(StreamExitRequest request: pendingRequests) {
|
||||
if(!request.isReserved()) {
|
||||
result.add(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
||||
|
||||
public class PredictedPortTarget implements ExitTarget {
|
||||
|
||||
final int port;
|
||||
|
||||
public PredictedPortTarget(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public boolean isAddressTarget() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public IPv4Address getAddress() {
|
||||
return new IPv4Address(0);
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
}
|
170
src/java/com/subgraph/orchid/circuits/StreamExitRequest.java
Normal file
170
src/java/com/subgraph/orchid/circuits/StreamExitRequest.java
Normal file
@ -0,0 +1,170 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.data.exitpolicy.ExitTarget;
|
||||
import com.subgraph.orchid.misc.GuardedBy;
|
||||
|
||||
public class StreamExitRequest implements ExitTarget {
|
||||
|
||||
private enum CompletionStatus {NOT_COMPLETED, SUCCESS, TIMEOUT, STREAM_OPEN_FAILURE, EXIT_FAILURE, INTERRUPTED};
|
||||
|
||||
private final boolean isAddress;
|
||||
private final IPv4Address address;
|
||||
private final String hostname;
|
||||
private final int port;
|
||||
private final Object requestCompletionLock;
|
||||
|
||||
@GuardedBy("requestCompletionLock") private CompletionStatus completionStatus;
|
||||
@GuardedBy("requestCompletionLock") private Stream stream;
|
||||
@GuardedBy("requestCompletionLock") private int streamOpenFailReason;
|
||||
|
||||
@GuardedBy("this") private boolean isReserved;
|
||||
@GuardedBy("this") private int retryCount;
|
||||
@GuardedBy("this") private long specificTimeout;
|
||||
|
||||
StreamExitRequest(Object requestCompletionLock, IPv4Address address, int port) {
|
||||
this(requestCompletionLock, true, "", address, port);
|
||||
}
|
||||
|
||||
StreamExitRequest(Object requestCompletionLock, String hostname, int port) {
|
||||
this(requestCompletionLock, false, hostname, null, port);
|
||||
}
|
||||
|
||||
private StreamExitRequest(Object requestCompletionLock, boolean isAddress, String hostname, IPv4Address address, int port) {
|
||||
this.requestCompletionLock = requestCompletionLock;
|
||||
this.isAddress = isAddress;
|
||||
this.hostname = hostname;
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.completionStatus = CompletionStatus.NOT_COMPLETED;
|
||||
}
|
||||
|
||||
public boolean isAddressTarget() {
|
||||
return isAddress;
|
||||
}
|
||||
|
||||
public IPv4Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public synchronized void setStreamTimeout(long timeout) {
|
||||
specificTimeout = timeout;
|
||||
}
|
||||
|
||||
public synchronized long getStreamTimeout() {
|
||||
if(specificTimeout > 0) {
|
||||
return specificTimeout;
|
||||
} else if(retryCount < 2) {
|
||||
return 10 * 1000;
|
||||
} else {
|
||||
return 15 * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
void setCompletedTimeout() {
|
||||
synchronized (requestCompletionLock) {
|
||||
newStatus(CompletionStatus.TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
void setExitFailed() {
|
||||
synchronized (requestCompletionLock) {
|
||||
newStatus(CompletionStatus.EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void setStreamOpenFailure(int reason) {
|
||||
synchronized (requestCompletionLock) {
|
||||
streamOpenFailReason = reason;
|
||||
newStatus(CompletionStatus.STREAM_OPEN_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void setCompletedSuccessfully(Stream stream) {
|
||||
synchronized (requestCompletionLock) {
|
||||
this.stream = stream;
|
||||
newStatus(CompletionStatus.SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
void setInterrupted() {
|
||||
synchronized (requestCompletionLock) {
|
||||
newStatus(CompletionStatus.INTERRUPTED);
|
||||
}
|
||||
}
|
||||
|
||||
private void newStatus(CompletionStatus newStatus) {
|
||||
if(completionStatus != CompletionStatus.NOT_COMPLETED) {
|
||||
throw new IllegalStateException("Attempt to set completion state to " + newStatus +" while status is "+ completionStatus);
|
||||
}
|
||||
completionStatus = newStatus;
|
||||
requestCompletionLock.notifyAll();
|
||||
}
|
||||
|
||||
|
||||
Stream getStream() throws OpenFailedException, TimeoutException, StreamConnectFailedException, InterruptedException {
|
||||
synchronized(requestCompletionLock) {
|
||||
switch(completionStatus) {
|
||||
case NOT_COMPLETED:
|
||||
throw new IllegalStateException("Request not completed");
|
||||
case EXIT_FAILURE:
|
||||
throw new OpenFailedException("Failure at exit node");
|
||||
case TIMEOUT:
|
||||
throw new TimeoutException();
|
||||
case STREAM_OPEN_FAILURE:
|
||||
throw new StreamConnectFailedException(streamOpenFailReason);
|
||||
case INTERRUPTED:
|
||||
throw new InterruptedException();
|
||||
case SUCCESS:
|
||||
return stream;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown completion status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void resetForRetry() {
|
||||
synchronized (requestCompletionLock) {
|
||||
streamOpenFailReason = 0;
|
||||
completionStatus = CompletionStatus.NOT_COMPLETED;
|
||||
}
|
||||
retryCount += 1;
|
||||
isReserved = false;
|
||||
}
|
||||
|
||||
boolean isCompleted() {
|
||||
synchronized (requestCompletionLock) {
|
||||
return completionStatus != CompletionStatus.NOT_COMPLETED;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean reserveRequest() {
|
||||
if(isReserved) return false;
|
||||
isReserved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized boolean isReserved() {
|
||||
return isReserved;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if(isAddress)
|
||||
return address + ":"+ port;
|
||||
else
|
||||
return hostname + ":"+ port;
|
||||
}
|
||||
}
|
219
src/java/com/subgraph/orchid/circuits/StreamImpl.java
Normal file
219
src/java/com/subgraph/orchid/circuits/StreamImpl.java
Normal file
@ -0,0 +1,219 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Circuit;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderable;
|
||||
import com.subgraph.orchid.dashboard.DashboardRenderer;
|
||||
|
||||
public class StreamImpl implements Stream, DashboardRenderable {
|
||||
private final static Logger logger = Logger.getLogger(StreamImpl.class.getName());
|
||||
|
||||
private final static int STREAMWINDOW_START = 500;
|
||||
private final static int STREAMWINDOW_INCREMENT = 50;
|
||||
private final static int STREAMWINDOW_MAX_UNFLUSHED = 10;
|
||||
|
||||
private final CircuitImpl circuit;
|
||||
|
||||
private final int streamId;
|
||||
private final boolean autoclose;
|
||||
|
||||
private final CircuitNode targetNode;
|
||||
private final TorInputStream inputStream;
|
||||
private final TorOutputStream outputStream;
|
||||
|
||||
private boolean isClosed;
|
||||
private boolean relayEndReceived;
|
||||
private int relayEndReason;
|
||||
private boolean relayConnectedReceived;
|
||||
private final Object waitConnectLock = new Object();
|
||||
private final Object windowLock = new Object();
|
||||
private int packageWindow;
|
||||
private int deliverWindow;
|
||||
|
||||
private String streamTarget = "";
|
||||
|
||||
StreamImpl(CircuitImpl circuit, CircuitNode targetNode, int streamId, boolean autoclose) {
|
||||
this.circuit = circuit;
|
||||
this.targetNode = targetNode;
|
||||
this.streamId = streamId;
|
||||
this.autoclose = autoclose;
|
||||
this.inputStream = new TorInputStream(this);
|
||||
this.outputStream = new TorOutputStream(this);
|
||||
packageWindow = STREAMWINDOW_START;
|
||||
deliverWindow = STREAMWINDOW_START;
|
||||
}
|
||||
|
||||
void addInputCell(RelayCell cell) {
|
||||
if(isClosed)
|
||||
return;
|
||||
if(cell.getRelayCommand() == RelayCell.RELAY_END) {
|
||||
synchronized(waitConnectLock) {
|
||||
relayEndReason = cell.getByte();
|
||||
relayEndReceived = true;
|
||||
inputStream.addEndCell(cell);
|
||||
waitConnectLock.notifyAll();
|
||||
}
|
||||
} else if(cell.getRelayCommand() == RelayCell.RELAY_CONNECTED) {
|
||||
synchronized(waitConnectLock) {
|
||||
relayConnectedReceived = true;
|
||||
waitConnectLock.notifyAll();
|
||||
}
|
||||
} else if(cell.getRelayCommand() == RelayCell.RELAY_SENDME) {
|
||||
synchronized(windowLock) {
|
||||
packageWindow += STREAMWINDOW_INCREMENT;
|
||||
windowLock.notifyAll();
|
||||
}
|
||||
}
|
||||
else {
|
||||
inputStream.addInputCell(cell);
|
||||
synchronized(windowLock) {
|
||||
deliverWindow--;
|
||||
if(deliverWindow < 0)
|
||||
throw new TorException("Stream has negative delivery window");
|
||||
}
|
||||
considerSendingSendme();
|
||||
}
|
||||
}
|
||||
|
||||
private void considerSendingSendme() {
|
||||
synchronized(windowLock) {
|
||||
if(deliverWindow > (STREAMWINDOW_START - STREAMWINDOW_INCREMENT))
|
||||
return;
|
||||
|
||||
if(inputStream.unflushedCellCount() >= STREAMWINDOW_MAX_UNFLUSHED)
|
||||
return;
|
||||
|
||||
final RelayCell sendme = circuit.createRelayCell(RelayCell.RELAY_SENDME, streamId, targetNode);
|
||||
circuit.sendRelayCell(sendme);
|
||||
deliverWindow += STREAMWINDOW_INCREMENT;
|
||||
}
|
||||
}
|
||||
|
||||
public int getStreamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public Circuit getCircuit() {
|
||||
return circuit;
|
||||
}
|
||||
|
||||
public CircuitNode getTargetNode() {
|
||||
return targetNode;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if(isClosed)
|
||||
return;
|
||||
|
||||
logger.fine("Closing stream "+ this);
|
||||
|
||||
isClosed = true;
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
circuit.removeStream(this);
|
||||
if(autoclose) {
|
||||
circuit.markForClose();
|
||||
}
|
||||
|
||||
if(!relayEndReceived) {
|
||||
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_END);
|
||||
cell.putByte(RelayCell.REASON_DONE);
|
||||
circuit.sendRelayCellToFinalNode(cell);
|
||||
}
|
||||
}
|
||||
|
||||
public void openDirectory(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
streamTarget = "[Directory]";
|
||||
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN_DIR);
|
||||
circuit.sendRelayCellToFinalNode(cell);
|
||||
waitForRelayConnected(timeout);
|
||||
}
|
||||
|
||||
void openExit(String target, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
streamTarget = target + ":"+ port;
|
||||
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN);
|
||||
cell.putString(target + ":"+ port);
|
||||
circuit.sendRelayCellToFinalNode(cell);
|
||||
waitForRelayConnected(timeout);
|
||||
}
|
||||
|
||||
private void waitForRelayConnected(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
|
||||
final long start = System.currentTimeMillis();
|
||||
long elapsed = 0;
|
||||
synchronized(waitConnectLock) {
|
||||
while(!relayConnectedReceived) {
|
||||
|
||||
if(relayEndReceived) {
|
||||
throw new StreamConnectFailedException(relayEndReason);
|
||||
}
|
||||
|
||||
if(elapsed >= timeout) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
|
||||
waitConnectLock.wait(timeout - elapsed);
|
||||
|
||||
elapsed = System.currentTimeMillis() - start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
public void waitForSendWindowAndDecrement() {
|
||||
waitForSendWindow(true);
|
||||
}
|
||||
|
||||
public void waitForSendWindow() {
|
||||
waitForSendWindow(false);
|
||||
}
|
||||
|
||||
public void waitForSendWindow(boolean decrement) {
|
||||
synchronized(windowLock) {
|
||||
while(packageWindow == 0) {
|
||||
try {
|
||||
windowLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new TorException("Thread interrupted while waiting for stream package window");
|
||||
}
|
||||
}
|
||||
if(decrement)
|
||||
packageWindow--;
|
||||
}
|
||||
targetNode.waitForSendWindow();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[Stream stream_id="+ 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());
|
||||
if(relayConnectedReceived) {
|
||||
writer.print(" sent="+outputStream.getBytesSent() + " recv="+ inputStream.getBytesReceived());
|
||||
} else {
|
||||
writer.print(" (waiting connect)");
|
||||
}
|
||||
writer.print(" target="+ streamTarget);
|
||||
writer.println("]");
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
||||
import com.subgraph.orchid.crypto.TorTapKeyAgreement;
|
||||
|
||||
public class TapCircuitExtender {
|
||||
private final static Logger logger = Logger.getLogger(TapCircuitExtender.class.getName());
|
||||
|
||||
private final CircuitExtender extender;
|
||||
private final TorTapKeyAgreement kex;
|
||||
private final Router router;
|
||||
|
||||
public TapCircuitExtender(CircuitExtender extender, Router router) {
|
||||
this.extender = extender;
|
||||
this.router = router;
|
||||
this.kex = new TorTapKeyAgreement(router.getOnionKey());
|
||||
}
|
||||
|
||||
public CircuitNode extendTo() {
|
||||
logger.fine("Extending to "+ router.getNickname() + " with TAP");
|
||||
final RelayCell cell = createRelayExtendCell();
|
||||
extender.sendRelayCell(cell);
|
||||
final RelayCell response = extender.receiveRelayResponse(RelayCell.RELAY_EXTENDED, router);
|
||||
if(response == null) {
|
||||
return null;
|
||||
}
|
||||
return processExtendResponse(response);
|
||||
}
|
||||
|
||||
private CircuitNode processExtendResponse(RelayCell response) {
|
||||
final byte[] handshakeResponse = new byte[TorTapKeyAgreement.DH_LEN + TorMessageDigest.TOR_DIGEST_SIZE];
|
||||
response.getByteArray(handshakeResponse);
|
||||
|
||||
final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE];
|
||||
final byte[] verifyDigest = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
|
||||
if(!kex.deriveKeysFromHandshakeResponse(handshakeResponse, keyMaterial, verifyDigest)) {
|
||||
return null;
|
||||
}
|
||||
return extender.createNewNode(router, keyMaterial, verifyDigest);
|
||||
}
|
||||
|
||||
private RelayCell createRelayExtendCell() {
|
||||
final RelayCell cell = extender.createRelayCell(RelayCell.RELAY_EXTEND);
|
||||
cell.putByteArray(router.getAddress().getAddressDataBytes());
|
||||
cell.putShort(router.getOnionPort());
|
||||
cell.putByteArray(kex.createOnionSkin());
|
||||
cell.putByteArray(router.getIdentityHash().getRawBytes());
|
||||
return cell;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.TorInitializationListener;
|
||||
|
||||
public class TorInitializationTracker {
|
||||
private final static Logger logger = Logger.getLogger(TorInitializationTracker.class.getName());
|
||||
private final static Map<Integer, String> messageMap = new HashMap<Integer, String>();
|
||||
|
||||
static {
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_STARTING, "Starting");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_CONN_DIR, "Connecting to directory server");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_HANDSHAKE_DIR, "Finishing handshake with directory server");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_ONEHOP_CREATE, "Establishing an encrypted directory connection");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_REQUESTING_STATUS, "Asking for networkstatus consensus");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_LOADING_STATUS, "Loading networkstatus consensus");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_REQUESTING_KEYS, "Asking for authority key certs");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_LOADING_KEYS, "Loading authority key certs");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, "Asking for relay descriptors");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, "Loading relay descriptors");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_CONN_OR, "Connecting to the Tor network");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_HANDSHAKE_OR, "Finished Handshake with first hop");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_CIRCUIT_CREATE, "Establishing a Tor circuit");
|
||||
messageMap.put(Tor.BOOTSTRAP_STATUS_DONE, "Done");
|
||||
}
|
||||
|
||||
private final List<TorInitializationListener> listeners = new ArrayList<TorInitializationListener>();
|
||||
|
||||
private final Object stateLock = new Object();
|
||||
private int bootstrapState = Tor.BOOTSTRAP_STATUS_STARTING;
|
||||
|
||||
|
||||
public void addListener(TorInitializationListener listener) {
|
||||
synchronized(listeners) {
|
||||
if(!listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(TorInitializationListener listener) {
|
||||
synchronized(listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public int getBootstrapState() {
|
||||
return bootstrapState;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
synchronized (stateLock) {
|
||||
bootstrapState = Tor.BOOTSTRAP_STATUS_STARTING;
|
||||
notifyListeners(Tor.BOOTSTRAP_STATUS_STARTING);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyEvent(int eventCode) {
|
||||
synchronized(stateLock) {
|
||||
if(eventCode <= bootstrapState || eventCode > 100) {
|
||||
return;
|
||||
}
|
||||
bootstrapState = eventCode;
|
||||
notifyListeners(eventCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListeners(int code) {
|
||||
final String message = getMessageForCode(code);
|
||||
for(TorInitializationListener listener: getListeners()) {
|
||||
try {
|
||||
listener.initializationProgress(message, code);
|
||||
if(code >= 100) {
|
||||
listener.initializationCompleted();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception occurred in TorInitializationListener callback: "+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getMessageForCode(int code) {
|
||||
if(messageMap.containsKey(code)) {
|
||||
return messageMap.get(code);
|
||||
} else {
|
||||
return "Unknown state";
|
||||
}
|
||||
}
|
||||
|
||||
private List<TorInitializationListener> getListeners() {
|
||||
synchronized (listeners) {
|
||||
return new ArrayList<TorInitializationListener>(listeners);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
228
src/java/com/subgraph/orchid/circuits/TorInputStream.java
Normal file
228
src/java/com/subgraph/orchid/circuits/TorInputStream.java
Normal file
@ -0,0 +1,228 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
||||
import com.subgraph.orchid.misc.GuardedBy;
|
||||
import com.subgraph.orchid.misc.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
public class TorInputStream extends InputStream {
|
||||
|
||||
private final static RelayCell CLOSE_SENTINEL = new RelayCellImpl(null, 0, 0, 0);
|
||||
private final static ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
|
||||
|
||||
private final Stream stream;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
/** Queue of RelayCells that have been received on this stream */
|
||||
@GuardedBy("lock") private final Queue<RelayCell> incomingCells;
|
||||
|
||||
/** Number of unread data bytes in current buffer and in RELAY_DATA cells on queue */
|
||||
@GuardedBy("lock") private int availableBytes;
|
||||
|
||||
/** Total number of data bytes received in RELAY_DATA cells on this stream */
|
||||
@GuardedBy("lock") private long bytesReceived;
|
||||
|
||||
/** Bytes of data from the RELAY_DATA cell currently being consumed */
|
||||
@GuardedBy("lock") private ByteBuffer currentBuffer;
|
||||
|
||||
/** Set when a RELAY_END cell is received */
|
||||
@GuardedBy("lock") private boolean isEOF;
|
||||
|
||||
/** Set when close() is called on this stream */
|
||||
@GuardedBy("lock") private boolean isClosed;
|
||||
|
||||
TorInputStream(Stream stream) {
|
||||
this.stream = stream;
|
||||
this.incomingCells = new LinkedList<RelayCell>();
|
||||
this.currentBuffer = EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
long getBytesReceived() {
|
||||
synchronized (lock) {
|
||||
return bytesReceived;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
synchronized (lock) {
|
||||
if(isClosed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
refillBufferIfNeeded();
|
||||
if(isEOF) {
|
||||
return -1;
|
||||
}
|
||||
availableBytes -= 1;
|
||||
return currentBuffer.get() & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
public synchronized int read(byte[] b, int off, int len) throws IOException {
|
||||
synchronized (lock) {
|
||||
if(isClosed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
|
||||
checkReadArguments(b, off, len);
|
||||
|
||||
if(len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
refillBufferIfNeeded();
|
||||
if(isEOF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int bytesRead = 0;
|
||||
int bytesRemaining = len;
|
||||
|
||||
while(bytesRemaining > 0 && !isEOF) {
|
||||
refillBufferIfNeeded();
|
||||
bytesRead += readFromCurrentBuffer(b, off + bytesRead, len - bytesRead);
|
||||
bytesRemaining = len - bytesRead;
|
||||
if(availableBytes == 0) {
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private int readFromCurrentBuffer(byte[] b, int off, int len) {
|
||||
final int readLength = (currentBuffer.remaining() >= len) ? (len) : (currentBuffer.remaining());
|
||||
currentBuffer.get(b, off, readLength);
|
||||
availableBytes -= readLength;
|
||||
return readLength;
|
||||
}
|
||||
|
||||
private void checkReadArguments(byte[] b, int off, int len) {
|
||||
if(b == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if( (off < 0) || (off >= b.length) || (len < 0) ||
|
||||
((off + len) > b.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
public int available() {
|
||||
synchronized(lock) {
|
||||
return availableBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
if(isClosed) {
|
||||
return;
|
||||
}
|
||||
isClosed = true;
|
||||
|
||||
incomingCells.add(CLOSE_SENTINEL);
|
||||
lock.notifyAll();
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
|
||||
void addEndCell(RelayCell cell) {
|
||||
synchronized (lock) {
|
||||
if(isClosed) {
|
||||
return;
|
||||
}
|
||||
incomingCells.add(cell);
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void addInputCell(RelayCell cell) {
|
||||
synchronized (lock) {
|
||||
if(isClosed) {
|
||||
return;
|
||||
}
|
||||
incomingCells.add(cell);
|
||||
bytesReceived += cell.cellBytesRemaining();
|
||||
availableBytes += cell.cellBytesRemaining();
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
// When this method (or fillBuffer()) returns either isEOF is set or currentBuffer has at least one byte to read
|
||||
private void refillBufferIfNeeded() throws IOException {
|
||||
if(!isEOF) {
|
||||
if(currentBuffer.hasRemaining()) {
|
||||
return;
|
||||
}
|
||||
fillBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void fillBuffer() throws IOException {
|
||||
while(true) {
|
||||
processIncomingCell(getNextCell());
|
||||
if(isEOF || currentBuffer.hasRemaining()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void processIncomingCell(RelayCell nextCell) throws IOException {
|
||||
if(isClosed || nextCell == CLOSE_SENTINEL) {
|
||||
throw new IOException("Input stream closed");
|
||||
}
|
||||
|
||||
switch(nextCell.getRelayCommand()) {
|
||||
case RelayCell.RELAY_DATA:
|
||||
currentBuffer = nextCell.getPayloadBuffer();
|
||||
break;
|
||||
case RelayCell.RELAY_END:
|
||||
currentBuffer = EMPTY_BUFFER;
|
||||
isEOF = true;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unexpected RelayCell command type in TorInputStream queue: "+ nextCell.getRelayCommand());
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private RelayCell getNextCell() throws IOException {
|
||||
try {
|
||||
while(incomingCells.isEmpty()) {
|
||||
lock.wait();
|
||||
}
|
||||
return incomingCells.remove();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Read interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
int unflushedCellCount() {
|
||||
synchronized (lock) {
|
||||
return incomingCells.size();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "TorInputStream stream="+ stream.getStreamId() +" node="+ stream.getTargetNode();
|
||||
}
|
||||
}
|
85
src/java/com/subgraph/orchid/circuits/TorOutputStream.java
Normal file
85
src/java/com/subgraph/orchid/circuits/TorOutputStream.java
Normal file
@ -0,0 +1,85 @@
|
||||
package com.subgraph.orchid.circuits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
|
||||
|
||||
public class TorOutputStream extends OutputStream {
|
||||
|
||||
private final StreamImpl stream;
|
||||
private RelayCell currentOutputCell;
|
||||
private volatile boolean isClosed;
|
||||
private long bytesSent;
|
||||
|
||||
TorOutputStream(StreamImpl stream) {
|
||||
this.stream = stream;
|
||||
this.bytesSent = 0;
|
||||
}
|
||||
|
||||
private void flushCurrentOutputCell() {
|
||||
if(currentOutputCell != null && currentOutputCell.cellBytesConsumed() > RelayCell.HEADER_SIZE) {
|
||||
stream.waitForSendWindowAndDecrement();
|
||||
stream.getCircuit().sendRelayCell(currentOutputCell);
|
||||
bytesSent += (currentOutputCell.cellBytesConsumed() - RelayCell.HEADER_SIZE);
|
||||
}
|
||||
|
||||
currentOutputCell = new RelayCellImpl(stream.getTargetNode(), stream.getCircuit().getCircuitId(),
|
||||
stream.getStreamId(), RelayCell.RELAY_DATA);
|
||||
}
|
||||
|
||||
long getBytesSent() {
|
||||
return bytesSent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int b) throws IOException {
|
||||
checkOpen();
|
||||
if(currentOutputCell == null || currentOutputCell.cellBytesRemaining() == 0)
|
||||
flushCurrentOutputCell();
|
||||
currentOutputCell.putByte(b);
|
||||
}
|
||||
|
||||
public synchronized void write(byte[] data, int offset, int length) throws IOException {
|
||||
checkOpen();
|
||||
if(currentOutputCell == null || currentOutputCell.cellBytesRemaining() == 0)
|
||||
flushCurrentOutputCell();
|
||||
|
||||
while(length > 0) {
|
||||
if(length < currentOutputCell.cellBytesRemaining()) {
|
||||
currentOutputCell.putByteArray(data, offset, length);
|
||||
return;
|
||||
}
|
||||
final int writeCount = currentOutputCell.cellBytesRemaining();
|
||||
currentOutputCell.putByteArray(data, offset, writeCount);
|
||||
flushCurrentOutputCell();
|
||||
offset += writeCount;
|
||||
length -= writeCount;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOpen() throws IOException {
|
||||
if(isClosed)
|
||||
throw new IOException("Output stream is closed");
|
||||
}
|
||||
|
||||
public synchronized void flush() {
|
||||
if(isClosed)
|
||||
return;
|
||||
flushCurrentOutputCell();
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if(isClosed)
|
||||
return;
|
||||
flush();
|
||||
isClosed = true;
|
||||
currentOutputCell = null;
|
||||
stream.close();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "TorOutputStream stream="+ stream.getStreamId() +" node="+ stream.getTargetNode();
|
||||
}
|
||||
}
|
215
src/java/com/subgraph/orchid/circuits/cells/CellImpl.java
Normal file
215
src/java/com/subgraph/orchid/circuits/cells/CellImpl.java
Normal file
@ -0,0 +1,215 @@
|
||||
package com.subgraph.orchid.circuits.cells;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
|
||||
public class CellImpl implements Cell {
|
||||
|
||||
public static CellImpl createCell(int circuitId, int command) {
|
||||
return new CellImpl(circuitId, command);
|
||||
}
|
||||
|
||||
public static CellImpl createVarCell(int circuitId, int command, int payloadLength) {
|
||||
return new CellImpl(circuitId, command, payloadLength);
|
||||
}
|
||||
|
||||
public static CellImpl readFromInputStream(InputStream input) throws IOException {
|
||||
final ByteBuffer header = readHeaderFromInputStream(input);
|
||||
final int circuitId = header.getShort() & 0xFFFF;
|
||||
final int command = header.get() & 0xFF;
|
||||
|
||||
if(command == VERSIONS || command > 127) {
|
||||
return readVarCell(circuitId, command, input);
|
||||
}
|
||||
|
||||
final CellImpl cell = new CellImpl(circuitId, command);
|
||||
readAll(input, cell.getCellBytes(), CELL_HEADER_LEN, CELL_PAYLOAD_LEN);
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
private static ByteBuffer readHeaderFromInputStream(InputStream input) throws IOException {
|
||||
final byte[] cellHeader = new byte[CELL_HEADER_LEN];
|
||||
readAll(input, cellHeader);
|
||||
return ByteBuffer.wrap(cellHeader);
|
||||
}
|
||||
|
||||
private static CellImpl readVarCell(int circuitId, int command, InputStream input) throws IOException {
|
||||
final byte[] lengthField = new byte[2];
|
||||
readAll(input, lengthField);
|
||||
final int length = ((lengthField[0] & 0xFF) << 8) | (lengthField[1] & 0xFF);
|
||||
CellImpl cell = new CellImpl(circuitId, command, length);
|
||||
readAll(input, cell.getCellBytes(), CELL_VAR_HEADER_LEN, length);
|
||||
return cell;
|
||||
}
|
||||
|
||||
private static void readAll(InputStream input, byte[] buffer) throws IOException {
|
||||
readAll(input, buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
private static void readAll(InputStream input, byte[] buffer, int offset, int length) throws IOException {
|
||||
int bytesRead = 0;
|
||||
while(bytesRead < length) {
|
||||
final int n = input.read(buffer, offset + bytesRead, length - bytesRead);
|
||||
if(n == -1)
|
||||
throw new EOFException();
|
||||
bytesRead += n;
|
||||
}
|
||||
}
|
||||
|
||||
private final int circuitId;
|
||||
private final int command;
|
||||
protected final ByteBuffer cellBuffer;
|
||||
|
||||
/* Variable length cell constructor (ie: VERSIONS cells only) */
|
||||
private CellImpl(int circuitId, int command, int payloadLength) {
|
||||
this.circuitId = circuitId;
|
||||
this.command = command;
|
||||
this.cellBuffer = ByteBuffer.wrap(new byte[CELL_VAR_HEADER_LEN + payloadLength]);
|
||||
cellBuffer.putShort((short)circuitId);
|
||||
cellBuffer.put((byte)command);
|
||||
cellBuffer.putShort((short) payloadLength);
|
||||
cellBuffer.mark();
|
||||
}
|
||||
|
||||
/* Fixed length cell constructor */
|
||||
protected CellImpl(int circuitId, int command) {
|
||||
this.circuitId = circuitId;
|
||||
this.command = command;
|
||||
this.cellBuffer = ByteBuffer.wrap(new byte[CELL_LEN]);
|
||||
cellBuffer.putShort((short) circuitId);
|
||||
cellBuffer.put((byte) command);
|
||||
cellBuffer.mark();
|
||||
}
|
||||
|
||||
protected CellImpl(byte[] rawCell) {
|
||||
this.cellBuffer = ByteBuffer.wrap(rawCell);
|
||||
this.circuitId = cellBuffer.getShort() & 0xFFFF;
|
||||
this.command = cellBuffer.get() & 0xFF;
|
||||
cellBuffer.mark();
|
||||
}
|
||||
|
||||
public int getCircuitId() {
|
||||
return circuitId;
|
||||
}
|
||||
|
||||
public int getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void resetToPayload() {
|
||||
cellBuffer.reset();
|
||||
}
|
||||
|
||||
public int getByte() {
|
||||
return cellBuffer.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int getByteAt(int index) {
|
||||
return cellBuffer.get(index) & 0xFF;
|
||||
}
|
||||
|
||||
public int getShort() {
|
||||
return cellBuffer.getShort() & 0xFFFF;
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
return cellBuffer.getInt();
|
||||
}
|
||||
|
||||
public int getShortAt(int index) {
|
||||
return cellBuffer.getShort(index) & 0xFFFF;
|
||||
}
|
||||
|
||||
public void getByteArray(byte[] buffer) {
|
||||
cellBuffer.get(buffer);
|
||||
}
|
||||
|
||||
public int cellBytesConsumed() {
|
||||
return cellBuffer.position();
|
||||
}
|
||||
|
||||
public int cellBytesRemaining() {
|
||||
return cellBuffer.remaining();
|
||||
}
|
||||
|
||||
public void putByte(int value) {
|
||||
cellBuffer.put((byte) value);
|
||||
}
|
||||
|
||||
public void putByteAt(int index, int value) {
|
||||
cellBuffer.put(index, (byte) value);
|
||||
}
|
||||
|
||||
public void putShort(int value) {
|
||||
cellBuffer.putShort((short) value);
|
||||
}
|
||||
|
||||
public void putShortAt(int index, int value) {
|
||||
cellBuffer.putShort(index, (short) value);
|
||||
}
|
||||
|
||||
public void putInt(int value) {
|
||||
cellBuffer.putInt(value);
|
||||
}
|
||||
|
||||
public void putString(String string) {
|
||||
final byte[] bytes = new byte[string.length() + 1];
|
||||
for(int i = 0; i < string.length(); i++)
|
||||
bytes[i] = (byte) string.charAt(i);
|
||||
putByteArray(bytes);
|
||||
}
|
||||
|
||||
public void putByteArray(byte[] data) {
|
||||
cellBuffer.put(data);
|
||||
}
|
||||
|
||||
public void putByteArray(byte[] data, int offset, int length) {
|
||||
cellBuffer.put(data, offset, length);
|
||||
}
|
||||
|
||||
public byte[] getCellBytes() {
|
||||
return cellBuffer.array();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Cell: circuit_id="+ circuitId +" command="+ command +" payload_len="+ cellBuffer.position();
|
||||
}
|
||||
|
||||
public static String errorToDescription(int errorCode) {
|
||||
switch(errorCode) {
|
||||
case ERROR_NONE:
|
||||
return "No error reason given";
|
||||
case ERROR_PROTOCOL:
|
||||
return "Tor protocol violation";
|
||||
case ERROR_INTERNAL:
|
||||
return "Internal error";
|
||||
case ERROR_REQUESTED:
|
||||
return "Response to a TRUNCATE command sent from client";
|
||||
case ERROR_HIBERNATING:
|
||||
return "Not currently operating; trying to save bandwidth.";
|
||||
case ERROR_RESOURCELIMIT:
|
||||
return "Out of memory, sockets, or circuit IDs.";
|
||||
case ERROR_CONNECTFAILED:
|
||||
return "Unable to reach server.";
|
||||
case ERROR_OR_IDENTITY:
|
||||
return "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.";
|
||||
case ERROR_FINISHED:
|
||||
return "The circuit has expired for being dirty or old.";
|
||||
case ERROR_TIMEOUT:
|
||||
return "Circuit construction took too long.";
|
||||
case ERROR_DESTROYED:
|
||||
return "The circuit was destroyed without client TRUNCATE";
|
||||
case ERROR_NOSUCHSERVICE:
|
||||
return "Request for unknown hidden service";
|
||||
default:
|
||||
return "Error code "+ errorCode;
|
||||
}
|
||||
}
|
||||
}
|
180
src/java/com/subgraph/orchid/circuits/cells/RelayCellImpl.java
Normal file
180
src/java/com/subgraph/orchid/circuits/cells/RelayCellImpl.java
Normal file
@ -0,0 +1,180 @@
|
||||
package com.subgraph.orchid.circuits.cells;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.subgraph.orchid.Cell;
|
||||
import com.subgraph.orchid.CircuitNode;
|
||||
import com.subgraph.orchid.RelayCell;
|
||||
import com.subgraph.orchid.TorException;
|
||||
|
||||
public class RelayCellImpl extends CellImpl implements RelayCell {
|
||||
|
||||
public static RelayCell createFromCell(CircuitNode node, Cell cell) {
|
||||
if(cell.getCommand() != Cell.RELAY)
|
||||
throw new TorException("Attempted to create RelayCell from Cell type: "+ cell.getCommand());
|
||||
return new RelayCellImpl(node, cell.getCellBytes());
|
||||
}
|
||||
|
||||
private final int streamId;
|
||||
private final int relayCommand;
|
||||
private final CircuitNode circuitNode;
|
||||
private final boolean isOutgoing;
|
||||
|
||||
/*
|
||||
* The payload of each unencrypted RELAY cell consists of:
|
||||
* Relay command [1 byte]
|
||||
* 'Recognized' [2 bytes]
|
||||
* StreamID [2 bytes]
|
||||
* Digest [4 bytes]
|
||||
* Length [2 bytes]
|
||||
* Data [CELL_LEN-14 bytes]
|
||||
*/
|
||||
|
||||
public RelayCellImpl(CircuitNode node, int circuit, int stream, int relayCommand) {
|
||||
this(node, circuit, stream, relayCommand, false);
|
||||
}
|
||||
|
||||
public RelayCellImpl(CircuitNode node, int circuit, int stream, int relayCommand, boolean isRelayEarly) {
|
||||
super(circuit, (isRelayEarly) ? (Cell.RELAY_EARLY) : (Cell.RELAY));
|
||||
this.circuitNode = node;
|
||||
this.relayCommand = relayCommand;
|
||||
this.streamId = stream;
|
||||
this.isOutgoing = true;
|
||||
putByte(relayCommand); // Command
|
||||
putShort(0); // 'Recognized'
|
||||
putShort(stream); // Stream
|
||||
putInt(0); // Digest
|
||||
putShort(0); // Length
|
||||
}
|
||||
|
||||
private RelayCellImpl(CircuitNode node, byte[] rawCell) {
|
||||
super(rawCell);
|
||||
this.circuitNode = node;
|
||||
this.relayCommand = getByte();
|
||||
getShort();
|
||||
this.streamId = getShort();
|
||||
this.isOutgoing = false;
|
||||
getInt();
|
||||
int payloadLength = getShort();
|
||||
cellBuffer.mark(); // End of header
|
||||
if(RelayCell.HEADER_SIZE + payloadLength > rawCell.length)
|
||||
throw new TorException("Header length field exceeds total size of cell");
|
||||
cellBuffer.limit(RelayCell.HEADER_SIZE + payloadLength);
|
||||
}
|
||||
|
||||
public int getStreamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getRelayCommand() {
|
||||
return relayCommand;
|
||||
}
|
||||
|
||||
public void setLength() {
|
||||
putShortAt(LENGTH_OFFSET, (short) (cellBytesConsumed() - HEADER_SIZE));
|
||||
}
|
||||
|
||||
public void setDigest(byte[] digest) {
|
||||
for(int i = 0; i < 4; i++)
|
||||
putByteAt(DIGEST_OFFSET + i, digest[i]);
|
||||
}
|
||||
|
||||
public ByteBuffer getPayloadBuffer() {
|
||||
final ByteBuffer dup = cellBuffer.duplicate();
|
||||
dup.reset();
|
||||
return dup.slice();
|
||||
}
|
||||
|
||||
public CircuitNode getCircuitNode() {
|
||||
return circuitNode;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if(isOutgoing)
|
||||
return "["+ commandToDescription(relayCommand) +" stream="+ streamId +" payload_len="+ (cellBytesConsumed() - HEADER_SIZE) +" dest="+ circuitNode +"]";
|
||||
else
|
||||
return "["+ commandToString() + " stream="+ streamId + " payload_len="+ cellBuffer.remaining() +" source="+ circuitNode + "]";
|
||||
}
|
||||
|
||||
public String commandToString() {
|
||||
if(relayCommand == RELAY_TRUNCATED) {
|
||||
final int code = getByteAt(HEADER_SIZE);
|
||||
return commandToDescription(relayCommand) + " ("+ CellImpl.errorToDescription(code) +")";
|
||||
} else if(relayCommand == RELAY_END) {
|
||||
final int code = getByteAt(HEADER_SIZE);
|
||||
return commandToDescription(relayCommand) +" ("+ reasonToDescription(code) +")";
|
||||
}
|
||||
else
|
||||
return commandToDescription(relayCommand);
|
||||
}
|
||||
|
||||
public static String reasonToDescription(int reasonCode) {
|
||||
switch(reasonCode) {
|
||||
case REASON_MISC:
|
||||
return "Unlisted reason";
|
||||
case REASON_RESOLVEFAILED:
|
||||
return "Couldn't look up hostname";
|
||||
case REASON_CONNECTREFUSED:
|
||||
return "Remote host refused connection";
|
||||
case REASON_EXITPOLICY:
|
||||
return "OR refuses to connect to host or port";
|
||||
case REASON_DESTROY:
|
||||
return "Circuit is being destroyed";
|
||||
case REASON_DONE:
|
||||
return "Anonymized TCP connection was closed";
|
||||
case REASON_TIMEOUT:
|
||||
return "Connection timed out, or OR timed out while connecting";
|
||||
case REASON_HIBERNATING:
|
||||
return "OR is temporarily hibernating";
|
||||
case REASON_INTERNAL:
|
||||
return "Internal error at the OR";
|
||||
case REASON_RESOURCELIMIT:
|
||||
return "OR has no resources to fulfill request";
|
||||
case REASON_CONNRESET:
|
||||
return "Connection was unexpectedly reset";
|
||||
case REASON_TORPROTOCOL:
|
||||
return "Tor protocol violation";
|
||||
case REASON_NOTDIRECTORY:
|
||||
return "Client sent RELAY_BEGIN_DIR to a non-directory server.";
|
||||
default:
|
||||
return "Reason code "+ reasonCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static String commandToDescription(int command) {
|
||||
switch(command) {
|
||||
case RELAY_BEGIN:
|
||||
return "RELAY_BEGIN";
|
||||
case RELAY_DATA:
|
||||
return "RELAY_DATA";
|
||||
case RELAY_END:
|
||||
return "RELAY_END";
|
||||
case RELAY_CONNECTED:
|
||||
return "RELAY_CONNECTED";
|
||||
case RELAY_SENDME:
|
||||
return "RELAY_SENDME";
|
||||
case RELAY_EXTEND:
|
||||
return "RELAY_EXTEND";
|
||||
case RELAY_EXTENDED:
|
||||
return "RELAY_EXTENDED";
|
||||
case RELAY_TRUNCATE:
|
||||
return "RELAY_TRUNCATE";
|
||||
case RELAY_TRUNCATED:
|
||||
return "RELAY_TRUNCATED";
|
||||
case RELAY_DROP:
|
||||
return "RELAY_DROP";
|
||||
case RELAY_RESOLVE:
|
||||
return "RELAY_RESOLVE";
|
||||
case RELAY_RESOLVED:
|
||||
return "RELAY_RESOLVED";
|
||||
case RELAY_BEGIN_DIR:
|
||||
return "RELAY_BEGIN_DIR";
|
||||
case RELAY_EXTEND2:
|
||||
return "RELAY_EXTEND2";
|
||||
case RELAY_EXTENDED2:
|
||||
return "RELAY_EXTENDED2";
|
||||
default:
|
||||
return "Relay command = "+ command;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
package com.subgraph.orchid.circuits.guards;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import com.subgraph.orchid.BridgeRouter;
|
||||
import com.subgraph.orchid.Descriptor;
|
||||
import com.subgraph.orchid.RouterDescriptor;
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
import com.subgraph.orchid.geoip.CountryCodeService;
|
||||
|
||||
public class BridgeRouterImpl implements BridgeRouter {
|
||||
private final IPv4Address address;
|
||||
private final int port;
|
||||
|
||||
private HexDigest identity;
|
||||
private Descriptor descriptor;
|
||||
|
||||
private volatile String cachedCountryCode;
|
||||
|
||||
BridgeRouterImpl(IPv4Address address, int port) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public IPv4Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public HexDigest getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public void setIdentity(HexDigest identity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
public void setDescriptor(RouterDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((address == null) ? 0 : address.hashCode());
|
||||
result = prime * result + port;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
BridgeRouterImpl other = (BridgeRouterImpl) obj;
|
||||
if (address == null) {
|
||||
if (other.address != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!address.equals(other.address)) {
|
||||
return false;
|
||||
}
|
||||
if (port != other.port) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
public String getCountryCode() {
|
||||
String cc = cachedCountryCode;
|
||||
if(cc == null) {
|
||||
cc = CountryCodeService.getInstance().getCountryCodeForAddress(getAddress());
|
||||
cachedCountryCode = cc;
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
public int getOnionPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public int getDirectoryPort() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public TorPublicKey getIdentityKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public HexDigest getIdentityHash() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public boolean isDescriptorDownloadable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public Descriptor getCurrentDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public HexDigest getDescriptorDigest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public HexDigest getMicrodescriptorDigest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public TorPublicKey getOnionKey() {
|
||||
if(descriptor != null) {
|
||||
return descriptor.getOnionKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getNTorOnionKey() {
|
||||
if(descriptor != null) {
|
||||
return descriptor.getNTorOnionKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasBandwidth() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getEstimatedBandwidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getMeasuredBandwidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Set<String> getFamilyMembers() {
|
||||
if(descriptor != null) {
|
||||
return descriptor.getFamilyMembers();
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
public int getAverageBandwidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getBurstBandwidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getObservedBandwidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isHibernating() {
|
||||
if(descriptor instanceof RouterDescriptor) {
|
||||
return ((RouterDescriptor)descriptor).isHibernating();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isBadExit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPossibleGuard() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isExit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFast() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isStable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isHSDirectory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean exitPolicyAccepts(IPv4Address address, int port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean exitPolicyAccepts(int port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[Bridge "+ address + ":"+ port + "]";
|
||||
}
|
||||
}
|
163
src/java/com/subgraph/orchid/circuits/guards/Bridges.java
Normal file
163
src/java/com/subgraph/orchid/circuits/guards/Bridges.java
Normal file
@ -0,0 +1,163 @@
|
||||
package com.subgraph.orchid.circuits.guards;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.BridgeRouter;
|
||||
import com.subgraph.orchid.DirectoryDownloader;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.RouterDescriptor;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.config.TorConfigBridgeLine;
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryRequestFailedException;
|
||||
|
||||
public class Bridges {
|
||||
private static final Logger logger = Logger.getLogger(Bridges.class.getName());
|
||||
|
||||
private class DescriptorDownloader implements Runnable {
|
||||
|
||||
private final BridgeRouterImpl target;
|
||||
|
||||
DescriptorDownloader(BridgeRouterImpl target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
downloadDescriptor();
|
||||
} finally {
|
||||
decrementOutstandingTasks();
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadDescriptor() {
|
||||
logger.fine("Downloading descriptor for bridge: "+ target);
|
||||
try {
|
||||
final RouterDescriptor descriptor = directoryDownloader.downloadBridgeDescriptor(target);
|
||||
if(descriptor != null) {
|
||||
logger.fine("Descriptor received for bridge "+ target +". Adding to list of usable bridges");
|
||||
target.setDescriptor(descriptor);
|
||||
synchronized(lock) {
|
||||
bridgeRouters.add(target);
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
} catch (DirectoryRequestFailedException e) {
|
||||
logger.warning("Failed to download descriptor for bridge: "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void decrementOutstandingTasks() {
|
||||
if(outstandingDownloadTasks.decrementAndGet() == 0) {
|
||||
logger.fine("Initial descriptor fetch complete");
|
||||
synchronized(lock) {
|
||||
bridgesInitialized = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final TorConfig config;
|
||||
private final DirectoryDownloader directoryDownloader;
|
||||
|
||||
private final Set<BridgeRouterImpl> bridgeRouters;
|
||||
private final TorRandom random;
|
||||
private final Object lock;
|
||||
|
||||
/** Initialization started */
|
||||
private boolean bridgesInitializing;
|
||||
/** Initialization completed */
|
||||
private boolean bridgesInitialized;
|
||||
|
||||
private AtomicInteger outstandingDownloadTasks;
|
||||
|
||||
Bridges(TorConfig config, DirectoryDownloader directoryDownloader) {
|
||||
this.config = config;
|
||||
this.directoryDownloader = directoryDownloader;
|
||||
this.bridgeRouters = new HashSet<BridgeRouterImpl>();
|
||||
this.random = new TorRandom();
|
||||
this.lock = new Object();
|
||||
this.outstandingDownloadTasks = new AtomicInteger();
|
||||
}
|
||||
|
||||
BridgeRouter chooseRandomBridge(Set<Router> excluded) throws InterruptedException {
|
||||
|
||||
synchronized(lock) {
|
||||
if(!bridgesInitialized && !bridgesInitializing) {
|
||||
initializeBridges();
|
||||
}
|
||||
while(!bridgesInitialized && !hasCandidates(excluded)) {
|
||||
lock.wait();
|
||||
}
|
||||
final List<BridgeRouter> candidates = getCandidates(excluded);
|
||||
if(candidates.isEmpty()) {
|
||||
logger.warning("Bridges enabled but no usable bridges configured");
|
||||
return null;
|
||||
}
|
||||
return candidates.get(random.nextInt(candidates.size()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCandidates(Set<Router> excluded) {
|
||||
return !(getCandidates(excluded).isEmpty());
|
||||
}
|
||||
|
||||
private List<BridgeRouter> getCandidates(Set<Router> excluded) {
|
||||
if(bridgeRouters.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<BridgeRouter> candidates = new ArrayList<BridgeRouter>(bridgeRouters.size());
|
||||
for(BridgeRouter br: bridgeRouters) {
|
||||
if(!excluded.contains(br)) {
|
||||
candidates.add(br);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private void initializeBridges() {
|
||||
logger.fine("Initializing bridges...");
|
||||
synchronized(lock) {
|
||||
if(bridgesInitializing || bridgesInitialized) {
|
||||
return;
|
||||
}
|
||||
if(directoryDownloader == null) {
|
||||
throw new IllegalStateException("Cannot download bridge descriptors because DirectoryDownload instance not initialized");
|
||||
}
|
||||
bridgesInitializing = true;
|
||||
startAllDownloadTasks();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Runnable> createDownloadTasks() {
|
||||
final List<Runnable> tasks = new ArrayList<Runnable>();
|
||||
for(TorConfigBridgeLine line: config.getBridges()) {
|
||||
tasks.add(new DescriptorDownloader(createBridgeFromLine(line)));
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private void startAllDownloadTasks() {
|
||||
final List<Runnable> tasks = createDownloadTasks();
|
||||
outstandingDownloadTasks.set(tasks.size());
|
||||
for(Runnable r: tasks) {
|
||||
final Thread thread = new Thread(r);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private BridgeRouterImpl createBridgeFromLine(TorConfigBridgeLine line) {
|
||||
final BridgeRouterImpl bridge = new BridgeRouterImpl(line.getAddress(), line.getPort());
|
||||
if(line.getFingerprint() != null) {
|
||||
bridge.setIdentity(line.getFingerprint());
|
||||
}
|
||||
return bridge;
|
||||
}
|
||||
}
|
305
src/java/com/subgraph/orchid/circuits/guards/EntryGuards.java
Normal file
305
src/java/com/subgraph/orchid/circuits/guards/EntryGuards.java
Normal file
@ -0,0 +1,305 @@
|
||||
package com.subgraph.orchid.circuits.guards;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
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;
|
||||
|
||||
import com.subgraph.orchid.ConnectionCache;
|
||||
import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.DirectoryDownloader;
|
||||
import com.subgraph.orchid.GuardEntry;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.path.CircuitNodeChooser;
|
||||
import com.subgraph.orchid.circuits.path.CircuitNodeChooser.WeightRule;
|
||||
import com.subgraph.orchid.circuits.path.RouterFilter;
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
|
||||
public class EntryGuards {
|
||||
private final static Logger logger = Logger.getLogger(EntryGuards.class.getName());
|
||||
|
||||
private final static int MIN_USABLE_GUARDS = 2;
|
||||
private final static int NUM_ENTRY_GUARDS = 3;
|
||||
|
||||
private final TorConfig config;
|
||||
private final TorRandom random;
|
||||
private final CircuitNodeChooser nodeChooser;
|
||||
private final ConnectionCache connectionCache;
|
||||
private final Directory directory;
|
||||
private final Set<GuardEntry> pendingProbes;
|
||||
|
||||
private final Bridges bridges;
|
||||
private final Object lock;
|
||||
private final Executor executor;
|
||||
|
||||
public EntryGuards(TorConfig config, ConnectionCache connectionCache, DirectoryDownloader directoryDownloader, Directory directory) {
|
||||
this.config = config;
|
||||
this.random = new TorRandom();
|
||||
this.nodeChooser = new CircuitNodeChooser(config, directory);
|
||||
this.connectionCache = connectionCache;
|
||||
this.directory = directory;
|
||||
this.pendingProbes = new HashSet<GuardEntry>();
|
||||
this.bridges = new Bridges(config, directoryDownloader);
|
||||
this.lock = new Object();
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
public boolean isUsingBridges() {
|
||||
return config.getUseBridges();
|
||||
}
|
||||
|
||||
public Router chooseRandomGuard(Set<Router> excluded) throws InterruptedException {
|
||||
if(config.getUseBridges()) {
|
||||
return bridges.chooseRandomBridge(excluded);
|
||||
}
|
||||
|
||||
/*
|
||||
* path-spec 5.
|
||||
*
|
||||
* When choosing the first hop of a circuit, Tor chooses at random from among the first
|
||||
* NumEntryGuards (default 3) usable guards on the list. If there are not at least 2
|
||||
* usable guards on the list, Tor adds routers until there are, or until there are no
|
||||
* more usable routers to add.
|
||||
*/
|
||||
|
||||
final List<Router> usableGuards = getMinimumUsableGuards(excluded, MIN_USABLE_GUARDS);
|
||||
final int n = Math.min(usableGuards.size(), NUM_ENTRY_GUARDS);
|
||||
return usableGuards.get(random.nextInt(n));
|
||||
}
|
||||
|
||||
private List<Router> getMinimumUsableGuards(Set<Router> excluded, int minSize) throws InterruptedException {
|
||||
synchronized(lock) {
|
||||
testStatusOfAllGuards();
|
||||
while(true) {
|
||||
List<Router> usableGuards = getUsableGuardRouters(excluded);
|
||||
if(usableGuards.size() >= minSize) {
|
||||
return usableGuards;
|
||||
} else {
|
||||
maybeChooseNew(usableGuards.size(), minSize, getExcludedForChooseNew(excluded, usableGuards));
|
||||
}
|
||||
lock.wait(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void probeConnectionSucceeded(GuardEntry entry) {
|
||||
synchronized (lock) {
|
||||
pendingProbes.remove(entry);
|
||||
if(entry.isAdded()) {
|
||||
retestProbeSucceeded(entry);
|
||||
} else {
|
||||
initialProbeSucceeded(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void probeConnectionFailed(GuardEntry entry) {
|
||||
synchronized (lock) {
|
||||
pendingProbes.remove(entry);
|
||||
if(entry.isAdded()) {
|
||||
retestProbeFailed(entry);
|
||||
}
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/* all methods below called holding 'lock' */
|
||||
|
||||
private void retestProbeSucceeded(GuardEntry entry) {
|
||||
entry.clearDownSince();
|
||||
}
|
||||
|
||||
private void initialProbeSucceeded(GuardEntry entry) {
|
||||
logger.fine("Probe connection to "+ entry.getRouterForEntry() + " succeeded. Adding it as a new entry guard.");
|
||||
directory.addGuardEntry(entry);
|
||||
retestAllUnreachable();
|
||||
}
|
||||
|
||||
private void retestProbeFailed(GuardEntry entry) {
|
||||
entry.markAsDown();
|
||||
}
|
||||
|
||||
/*
|
||||
* path-spec 5.
|
||||
*
|
||||
* Additionally, Tor retries unreachable guards the first time it adds a new
|
||||
* guard to the list, since it is possible that the old guards were only marked
|
||||
* as unreachable because the network was unreachable or down.
|
||||
|
||||
*/
|
||||
private void retestAllUnreachable() {
|
||||
for(GuardEntry e: directory.getGuardEntries()) {
|
||||
if(e.getDownSince() != null) {
|
||||
launchEntryProbe(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testStatusOfAllGuards() {
|
||||
for(GuardEntry entry: directory.getGuardEntries()) {
|
||||
if(isPermanentlyUnlisted(entry) || isExpired(entry)) {
|
||||
directory.removeGuardEntry(entry);
|
||||
} else if(needsUnreachableTest(entry)) {
|
||||
launchEntryProbe(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Router> getUsableGuardRouters(Set<Router> excluded) {
|
||||
List<Router> usableRouters = new ArrayList<Router>();
|
||||
for(GuardEntry entry: directory.getGuardEntries()) {
|
||||
addRouterIfUsableAndNotExcluded(entry, excluded, usableRouters);
|
||||
}
|
||||
return usableRouters;
|
||||
}
|
||||
|
||||
private void addRouterIfUsableAndNotExcluded(GuardEntry entry, Set<Router> excluded, List<Router> routers) {
|
||||
if(entry.testCurrentlyUsable() && entry.getDownSince() == null) {
|
||||
final Router r = entry.getRouterForEntry();
|
||||
if(r != null && !excluded.contains(r)) {
|
||||
routers.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Router> getExcludedForChooseNew(Set<Router> excluded, List<Router> usable) {
|
||||
final Set<Router> set = new HashSet<Router>();
|
||||
set.addAll(excluded);
|
||||
set.addAll(usable);
|
||||
addPendingInitialConnections(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
private void addPendingInitialConnections(Set<Router> routerSet) {
|
||||
for(GuardEntry entry: pendingProbes) {
|
||||
if(!entry.isAdded()) {
|
||||
Router r = entry.getRouterForEntry();
|
||||
if(r != null) {
|
||||
routerSet.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeChooseNew(int usableSize, int minSize, Set<Router> excluded) {
|
||||
int sz = usableSize + countPendingInitialProbes();
|
||||
while(sz < minSize) {
|
||||
Router newGuard = chooseNewGuard(excluded);
|
||||
if(newGuard == null) {
|
||||
logger.warning("Need to add entry guards but no suitable guard routers are available");
|
||||
return;
|
||||
}
|
||||
logger.fine("Testing "+ newGuard + " as a new guard since we only have "+ usableSize + " usable guards");
|
||||
final GuardEntry entry = directory.createGuardEntryFor(newGuard);
|
||||
launchEntryProbe(entry);
|
||||
sz += 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int countPendingInitialProbes() {
|
||||
int count = 0;
|
||||
for(GuardEntry entry: pendingProbes) {
|
||||
if(!entry.isAdded()) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private Router chooseNewGuard(final Set<Router> excluded) {
|
||||
return nodeChooser.chooseRandomNode(WeightRule.WEIGHT_FOR_GUARD, new RouterFilter() {
|
||||
public boolean filter(Router router) {
|
||||
return router.isValid() && router.isPossibleGuard() && router.isRunning() && !excluded.contains(router);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void launchEntryProbe(GuardEntry entry) {
|
||||
if(!entry.testCurrentlyUsable() || pendingProbes.contains(entry)) {
|
||||
return;
|
||||
}
|
||||
pendingProbes.add(entry);
|
||||
executor.execute(new GuardProbeTask(connectionCache, this, entry));
|
||||
}
|
||||
|
||||
/*
|
||||
* path-spec 5.
|
||||
*
|
||||
* If the guard is excluded because of its status in the networkstatuses for
|
||||
* over 30 days, Tor removes it from the list entirely, preserving order.
|
||||
*/
|
||||
private boolean isPermanentlyUnlisted(GuardEntry entry) {
|
||||
final Date unlistedSince = entry.getUnlistedSince();
|
||||
if(unlistedSince == null || pendingProbes.contains(entry)) {
|
||||
return false;
|
||||
}
|
||||
final Date now = new Date();
|
||||
final long unlistedTime = now.getTime() - unlistedSince.getTime();
|
||||
return unlistedTime > THIRTY_DAYS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expire guards after 60 days since creation time.
|
||||
*/
|
||||
private boolean isExpired(GuardEntry entry) {
|
||||
final Date createdAt = entry.getCreatedTime();
|
||||
final Date now = new Date();
|
||||
final long createdAgo = now.getTime() - createdAt.getTime();
|
||||
return createdAgo > SIXTY_DAYS;
|
||||
}
|
||||
|
||||
private boolean needsUnreachableTest(GuardEntry entry) {
|
||||
final Date downSince = entry.getDownSince();
|
||||
if(downSince == null || !entry.testCurrentlyUsable()) {
|
||||
return false;
|
||||
}
|
||||
final Date now = new Date();
|
||||
final Date lastConnect = entry.getLastConnectAttempt();
|
||||
final long timeDown = now.getTime() - downSince.getTime();
|
||||
final long timeSinceLastRetest = (lastConnect == null) ? timeDown : (now.getTime() - lastConnect.getTime());
|
||||
|
||||
return timeSinceLastRetest > getRetestInterval(timeDown);
|
||||
}
|
||||
|
||||
private final static long ONE_HOUR = hoursToMs(1);
|
||||
private final static long FOUR_HOURS = hoursToMs(4);
|
||||
private final static long SIX_HOURS = hoursToMs(6);
|
||||
private final static long EIGHTEEN_HOURS = hoursToMs(18);
|
||||
private final static long THIRTYSIX_HOURS = hoursToMs(36);
|
||||
private final static long THREE_DAYS = daysToMs(3);
|
||||
private final static long SEVEN_DAYS = daysToMs(7);
|
||||
private final static long THIRTY_DAYS = daysToMs(30);
|
||||
private final static long SIXTY_DAYS = daysToMs(60);
|
||||
|
||||
private static long hoursToMs(long n) {
|
||||
return TimeUnit.MILLISECONDS.convert(n, TimeUnit.HOURS);
|
||||
}
|
||||
private static long daysToMs(long n) {
|
||||
return TimeUnit.MILLISECONDS.convert(n, TimeUnit.DAYS);
|
||||
}
|
||||
/*
|
||||
* path-spec 5.
|
||||
*
|
||||
* If Tor fails to connect to an otherwise usable guard, it retries
|
||||
* periodically: every hour for six hours, every 4 hours for 3 days, every
|
||||
* 18 hours for a week, and every 36 hours thereafter.
|
||||
*/
|
||||
|
||||
private long getRetestInterval(long timeDown) {
|
||||
if(timeDown < SIX_HOURS) {
|
||||
return ONE_HOUR;
|
||||
} else if(timeDown < THREE_DAYS) {
|
||||
return FOUR_HOURS;
|
||||
} else if(timeDown < SEVEN_DAYS) {
|
||||
return EIGHTEEN_HOURS;
|
||||
} else {
|
||||
return THIRTYSIX_HOURS;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.subgraph.orchid.circuits.guards;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.ConnectionCache;
|
||||
import com.subgraph.orchid.ConnectionIOException;
|
||||
import com.subgraph.orchid.GuardEntry;
|
||||
import com.subgraph.orchid.Router;
|
||||
|
||||
public class GuardProbeTask implements Runnable{
|
||||
private final static Logger logger = Logger.getLogger(GuardProbeTask.class.getName());
|
||||
private final ConnectionCache connectionCache;
|
||||
private final EntryGuards entryGuards;
|
||||
private final GuardEntry entry;
|
||||
|
||||
public GuardProbeTask(ConnectionCache connectionCache, EntryGuards entryGuards, GuardEntry entry) {
|
||||
this.connectionCache = connectionCache;
|
||||
this.entryGuards = entryGuards;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
final Router router = entry.getRouterForEntry();
|
||||
if(router == null) {
|
||||
entryGuards.probeConnectionFailed(entry);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
connectionCache.getConnectionTo(router, false);
|
||||
entryGuards.probeConnectionSucceeded(entry);
|
||||
return;
|
||||
} catch (ConnectionIOException e) {
|
||||
logger.fine("IO exception probing entry guard "+ router + " : "+ e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch(Exception e) {
|
||||
logger.log(Level.WARNING, "Unexpected exception probing entry guard: "+ e, e);
|
||||
}
|
||||
entryGuards.probeConnectionFailed(entry);
|
||||
}
|
||||
}
|
120
src/java/com/subgraph/orchid/circuits/hs/HSAuthentication.java
Normal file
120
src/java/com/subgraph/orchid/circuits/hs/HSAuthentication.java
Normal file
@ -0,0 +1,120 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.TorParsingException;
|
||||
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie.CookieType;
|
||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
||||
import com.subgraph.orchid.crypto.TorStreamCipher;
|
||||
|
||||
public class HSAuthentication {
|
||||
private final static int BASIC_ID_LENGTH = 4;
|
||||
private final HSDescriptorCookie cookie;
|
||||
|
||||
public HSAuthentication(HSDescriptorCookie cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
|
||||
public byte[] decryptIntroductionPoints(byte[] content) throws HSAuthenticationException {
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(content);
|
||||
final int firstByte = buffer.get() & 0xFF;
|
||||
if(firstByte == 1) {
|
||||
return decryptIntroductionPointsWithBasicAuth(buffer);
|
||||
} else if(firstByte == 2) {
|
||||
return decryptIntroductionPointsWithStealthAuth(buffer);
|
||||
} else {
|
||||
throw new HSAuthenticationException("Introduction points section begins with unrecognized byte ("+ firstByte +")");
|
||||
}
|
||||
}
|
||||
|
||||
private static class BasicAuthEntry {
|
||||
final byte[] id;
|
||||
final byte[] skey;
|
||||
BasicAuthEntry(byte[] id, byte[] skey) {
|
||||
this.id = id;
|
||||
this.skey = skey;
|
||||
}
|
||||
}
|
||||
|
||||
private BasicAuthEntry createEntry(ByteBuffer bb) {
|
||||
final byte[] id = new byte[BASIC_ID_LENGTH];
|
||||
final byte[] skey = new byte[TorStreamCipher.KEY_LEN];
|
||||
bb.get(id);
|
||||
bb.get(skey);
|
||||
return new BasicAuthEntry(id, skey);
|
||||
}
|
||||
|
||||
private byte[] decryptIntroductionPointsWithBasicAuth(ByteBuffer buffer) throws HSAuthenticationException {
|
||||
if(cookie == null || cookie.getType() != CookieType.COOKIE_BASIC) {
|
||||
throw new TorParsingException("Introduction points encrypted with 'basic' authentication and no cookie available to decrypt");
|
||||
}
|
||||
|
||||
final List<BasicAuthEntry> entries = readBasicEntries(buffer);
|
||||
final byte[] iv = readAuthIV(buffer);
|
||||
final byte[] id = generateAuthId(iv);
|
||||
final byte[] k = findKeyInAuthEntries(entries, id);
|
||||
|
||||
return decryptRemaining(buffer, k, iv);
|
||||
}
|
||||
|
||||
private List<BasicAuthEntry> readBasicEntries(ByteBuffer b) {
|
||||
final int blockCount = b.get() & 0xFF;
|
||||
final int entryCount = blockCount * 16;
|
||||
final List<BasicAuthEntry> entries = new ArrayList<BasicAuthEntry>(entryCount);
|
||||
for(int i = 0; i < entryCount; i++) {
|
||||
entries.add( createEntry(b) );
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
||||
private byte[] readAuthIV(ByteBuffer b) {
|
||||
final byte[] iv = new byte[16];
|
||||
b.get(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
private byte[] generateAuthId(byte[] iv) {
|
||||
final TorMessageDigest md = new TorMessageDigest();
|
||||
md.update(cookie.getValue());
|
||||
md.update(iv);
|
||||
final byte[] digest = md.getDigestBytes();
|
||||
final byte[] id = new byte[BASIC_ID_LENGTH];
|
||||
System.arraycopy(digest, 0, id, 0, BASIC_ID_LENGTH);
|
||||
return id;
|
||||
}
|
||||
|
||||
private byte[] findKeyInAuthEntries(List<BasicAuthEntry> entries, byte[] id) throws HSAuthenticationException {
|
||||
for(BasicAuthEntry e: entries) {
|
||||
if(Arrays.equals(id, e.id)) {
|
||||
return decryptAuthEntry(e);
|
||||
}
|
||||
}
|
||||
throw new HSAuthenticationException("Could not find matching cookie id for basic authentication");
|
||||
}
|
||||
|
||||
private byte[] decryptAuthEntry(BasicAuthEntry entry) throws HSAuthenticationException {
|
||||
TorStreamCipher cipher = TorStreamCipher.createFromKeyBytes(cookie.getValue());
|
||||
cipher.encrypt(entry.skey);
|
||||
return entry.skey;
|
||||
}
|
||||
|
||||
private byte[] decryptRemaining(ByteBuffer buffer, byte[] key, byte[] iv) {
|
||||
TorStreamCipher streamCipher = TorStreamCipher.createFromKeyBytesWithIV(key, iv);
|
||||
final byte[] remaining = new byte[buffer.remaining()];
|
||||
buffer.get(remaining);
|
||||
streamCipher.encrypt(remaining);
|
||||
return remaining;
|
||||
}
|
||||
|
||||
private byte[] decryptIntroductionPointsWithStealthAuth(ByteBuffer buffer) {
|
||||
if(cookie == null || cookie.getType() != CookieType.COOKIE_STEALTH) {
|
||||
throw new TorParsingException("Introduction points encrypted with 'stealth' authentication and no cookie available to descrypt");
|
||||
}
|
||||
final byte[] iv = readAuthIV(buffer);
|
||||
return decryptRemaining(buffer, cookie.getValue(), iv);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
public class HSAuthenticationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
HSAuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
HSAuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
109
src/java/com/subgraph/orchid/circuits/hs/HSDescriptor.java
Normal file
109
src/java/com/subgraph/orchid/circuits/hs/HSDescriptor.java
Normal file
@ -0,0 +1,109 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.Timestamp;
|
||||
|
||||
public class HSDescriptor {
|
||||
private final static long MS_24_HOURS = (24 * 60 * 60 * 1000);
|
||||
private final HiddenService hiddenService;
|
||||
private HexDigest descriptorId;
|
||||
private Timestamp publicationTime;
|
||||
private HexDigest secretIdPart;
|
||||
private TorPublicKey permanentKey;
|
||||
private int[] protocolVersions;
|
||||
private List<IntroductionPoint> introductionPoints;
|
||||
|
||||
public HSDescriptor(HiddenService hiddenService) {
|
||||
this.hiddenService = hiddenService;
|
||||
introductionPoints = new ArrayList<IntroductionPoint>();
|
||||
}
|
||||
|
||||
HiddenService getHiddenService() {
|
||||
return hiddenService;
|
||||
}
|
||||
|
||||
void setPublicationTime(Timestamp ts) {
|
||||
this.publicationTime = ts;
|
||||
}
|
||||
|
||||
void setSecretIdPart(HexDigest secretIdPart) {
|
||||
this.secretIdPart = secretIdPart;
|
||||
}
|
||||
|
||||
void setDescriptorId(HexDigest descriptorId) {
|
||||
this.descriptorId = descriptorId;
|
||||
}
|
||||
|
||||
void setPermanentKey(TorPublicKey permanentKey) {
|
||||
this.permanentKey = permanentKey;
|
||||
}
|
||||
|
||||
void setProtocolVersions(int[] protocolVersions) {
|
||||
this.protocolVersions = protocolVersions;
|
||||
}
|
||||
|
||||
void addIntroductionPoint(IntroductionPoint ip) {
|
||||
introductionPoints.add(ip);
|
||||
}
|
||||
|
||||
HexDigest getDescriptorId() {
|
||||
return descriptorId;
|
||||
}
|
||||
|
||||
int getVersion() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
TorPublicKey getPermanentKey() {
|
||||
return permanentKey;
|
||||
}
|
||||
|
||||
HexDigest getSecretIdPart() {
|
||||
return secretIdPart;
|
||||
}
|
||||
|
||||
Timestamp getPublicationTime() {
|
||||
return publicationTime;
|
||||
}
|
||||
|
||||
int[] getProtocolVersions() {
|
||||
return protocolVersions;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final long then = publicationTime.getTime();
|
||||
return (now - then) > MS_24_HOURS;
|
||||
}
|
||||
|
||||
List<IntroductionPoint> getIntroductionPoints() {
|
||||
return new ArrayList<IntroductionPoint>(introductionPoints);
|
||||
}
|
||||
|
||||
List<IntroductionPoint> getShuffledIntroductionPoints() {
|
||||
return shuffle(getIntroductionPoints());
|
||||
}
|
||||
|
||||
private List<IntroductionPoint> shuffle(List<IntroductionPoint> list) {
|
||||
final TorRandom r = new TorRandom();
|
||||
final int sz = list.size();
|
||||
for(int i = 0; i < sz; i++) {
|
||||
swap(list, i, r.nextInt(sz));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void swap(List<IntroductionPoint> list, int a, int b) {
|
||||
if(a == b) {
|
||||
return;
|
||||
}
|
||||
final IntroductionPoint tmp = list.get(a);
|
||||
list.set(a, list.get(b));
|
||||
list.set(b, tmp);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
public class HSDescriptorCookie {
|
||||
|
||||
public enum CookieType { COOKIE_BASIC, COOKIE_STEALTH };
|
||||
|
||||
private final CookieType type;
|
||||
private final byte[] value;
|
||||
|
||||
public HSDescriptorCookie(CookieType type, byte[] value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public byte getAuthTypeByte() {
|
||||
switch(type) {
|
||||
case COOKIE_BASIC:
|
||||
return 1;
|
||||
case COOKIE_STEALTH:
|
||||
return 2;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public CookieType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
|
||||
public class HSDescriptorDirectory {
|
||||
|
||||
private final HexDigest descriptorId;
|
||||
private final Router directory;
|
||||
|
||||
HSDescriptorDirectory(HexDigest descriptorId, Router directory) {
|
||||
this.descriptorId = descriptorId;
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
Router getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
HexDigest getDescriptorId() {
|
||||
return descriptorId;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return descriptorId + " : " + directory;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.DirectoryCircuit;
|
||||
import com.subgraph.orchid.InternalCircuit;
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.CircuitManagerImpl;
|
||||
import com.subgraph.orchid.directory.DocumentFieldParserImpl;
|
||||
import com.subgraph.orchid.directory.downloader.DirectoryRequestFailedException;
|
||||
import com.subgraph.orchid.directory.downloader.HttpConnection;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingResultHandler;
|
||||
|
||||
public class HSDescriptorDownloader {
|
||||
private final static Logger logger = Logger.getLogger(HSDescriptorDirectory.class.getName());
|
||||
|
||||
private final HiddenService hiddenService;
|
||||
private final CircuitManagerImpl circuitManager;
|
||||
private final List<HSDescriptorDirectory> directories;
|
||||
|
||||
public HSDescriptorDownloader(HiddenService hiddenService, CircuitManagerImpl circuitManager, List<HSDescriptorDirectory> directories) {
|
||||
this.hiddenService = hiddenService;
|
||||
this.circuitManager = circuitManager;
|
||||
this.directories = directories;
|
||||
}
|
||||
|
||||
|
||||
public HSDescriptor downloadDescriptor() {
|
||||
for(HSDescriptorDirectory d: directories) {
|
||||
HSDescriptor descriptor = downloadDescriptorFrom(d);
|
||||
if(descriptor != null) {
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
// All directories failed
|
||||
return null;
|
||||
}
|
||||
|
||||
private HSDescriptor downloadDescriptorFrom(HSDescriptorDirectory dd) {
|
||||
logger.fine("Downloading descriptor from "+ dd.getDirectory());
|
||||
|
||||
Stream stream = null;
|
||||
try {
|
||||
stream = openHSDirectoryStream(dd.getDirectory());
|
||||
HttpConnection http = new HttpConnection(stream);
|
||||
http.sendGetRequest("/tor/rendezvous2/"+ dd.getDescriptorId().toBase32());
|
||||
http.readResponse();
|
||||
if(http.getStatusCode() == 200) {
|
||||
return readDocument(dd, http.getMessageBody());
|
||||
} else {
|
||||
logger.fine("HS descriptor download for "+ hiddenService.getOnionAddressForLogging() + " failed with status "+ http.getStatusCode());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (TimeoutException e) {
|
||||
logger.fine("Timeout downloading HS descriptor from "+ dd.getDirectory());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
logger.info("IOException downloading HS descriptor from "+ dd.getDirectory() +" : "+ e);
|
||||
return null;
|
||||
} catch (OpenFailedException e) {
|
||||
logger.info("Failed to open stream to HS directory "+ dd.getDirectory() +" : "+ e.getMessage());
|
||||
return null;
|
||||
} catch (DirectoryRequestFailedException e) {
|
||||
logger.info("Directory request to HS directory "+ dd.getDirectory() + " failed "+ e.getMessage());
|
||||
return null;
|
||||
} finally {
|
||||
if(stream != null) {
|
||||
stream.close();
|
||||
stream.getCircuit().markForClose();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private Stream openHSDirectoryStream(Router directory) throws TimeoutException, InterruptedException, OpenFailedException {
|
||||
|
||||
final InternalCircuit circuit = circuitManager.getCleanInternalCircuit();
|
||||
|
||||
try {
|
||||
final DirectoryCircuit dc = circuit.cannibalizeToDirectory(directory);
|
||||
return dc.openDirectoryStream(10000, true);
|
||||
} catch (StreamConnectFailedException e) {
|
||||
circuit.markForClose();
|
||||
throw new OpenFailedException("Failed to open directory stream");
|
||||
} catch (TorException e) {
|
||||
circuit.markForClose();
|
||||
throw new OpenFailedException("Failed to extend circuit to HS directory: "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private HSDescriptor readDocument(HSDescriptorDirectory dd, ByteBuffer body) {
|
||||
DocumentFieldParserImpl fieldParser = new DocumentFieldParserImpl(body);
|
||||
HSDescriptorParser parser = new HSDescriptorParser(hiddenService, fieldParser, hiddenService.getAuthenticationCookie());
|
||||
DescriptorParseResult result = new DescriptorParseResult(dd);
|
||||
parser.parse(result);
|
||||
return result.getDescriptor();
|
||||
}
|
||||
|
||||
private static class DescriptorParseResult implements DocumentParsingResultHandler<HSDescriptor> {
|
||||
HSDescriptorDirectory dd;
|
||||
HSDescriptor descriptor;
|
||||
|
||||
public DescriptorParseResult(HSDescriptorDirectory dd) {
|
||||
this.dd = dd;
|
||||
}
|
||||
|
||||
HSDescriptor getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
public void documentParsed(HSDescriptor document) {
|
||||
this.descriptor = document;
|
||||
}
|
||||
|
||||
public void documentInvalid(HSDescriptor document, String message) {
|
||||
logger.info("Invalid HS descriptor document received from "+ dd.getDirectory() + " for descriptor "+ dd.getDescriptorId());
|
||||
}
|
||||
|
||||
public void parsingError(String message) {
|
||||
logger.info("Failed to parse HS descriptor document received from "+ dd.getDirectory() + " for descriptor "+ dd.getDescriptorId() + " : " + message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
public enum HSDescriptorKeyword {
|
||||
RENDEZVOUS_SERVICE_DESCRIPTOR("rendezvous-service-descriptor", 1),
|
||||
VERSION("version", 1),
|
||||
PERMANENT_KEY("permanent-key", 0),
|
||||
SECRET_ID_PART("secret-id-part", 1),
|
||||
PUBLICATION_TIME("publication-time", 2),
|
||||
PROTOCOL_VERSIONS("protocol-versions", 2),
|
||||
INTRODUCTION_POINTS("introduction-points", 0),
|
||||
SIGNATURE("signature", 0),
|
||||
UNKNOWN_KEYWORD("KEYWORD NOT FOUND", 0);
|
||||
|
||||
private final String keyword;
|
||||
private final int argumentCount;
|
||||
|
||||
HSDescriptorKeyword(String keyword, int argumentCount) {
|
||||
this.keyword = keyword;
|
||||
this.argumentCount = argumentCount;
|
||||
}
|
||||
|
||||
String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
int getArgumentCount() {
|
||||
return argumentCount;
|
||||
}
|
||||
|
||||
static HSDescriptorKeyword findKeyword(String keyword) {
|
||||
for(HSDescriptorKeyword k: values()) {
|
||||
if(k.getKeyword().equals(keyword)) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return UNKNOWN_KEYWORD;
|
||||
}
|
||||
}
|
160
src/java/com/subgraph/orchid/circuits/hs/HSDescriptorParser.java
Normal file
160
src/java/com/subgraph/orchid/circuits/hs/HSDescriptorParser.java
Normal file
@ -0,0 +1,160 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.TorParsingException;
|
||||
import com.subgraph.orchid.crypto.TorSignature;
|
||||
import com.subgraph.orchid.directory.DocumentFieldParserImpl;
|
||||
import com.subgraph.orchid.directory.parsing.BasicDocumentParsingResult;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentFieldParser;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentObject;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParser;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingHandler;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingResult;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingResultHandler;
|
||||
import com.subgraph.orchid.encoders.Base64;
|
||||
|
||||
public class HSDescriptorParser implements DocumentParser<HSDescriptor>{
|
||||
private static final Logger logger = Logger.getLogger(HSDescriptor.class.getName());
|
||||
|
||||
private final DocumentFieldParser fieldParser;
|
||||
private final HSDescriptor descriptor;
|
||||
private final HSAuthentication authentication;
|
||||
|
||||
private DocumentParsingResultHandler<HSDescriptor> resultHandler;
|
||||
|
||||
public HSDescriptorParser(HiddenService hiddenService, DocumentFieldParser fieldParser) {
|
||||
this(hiddenService, fieldParser, null);
|
||||
}
|
||||
|
||||
public HSDescriptorParser(HiddenService hiddenService, DocumentFieldParser fieldParser, HSDescriptorCookie cookie) {
|
||||
this.fieldParser = fieldParser;
|
||||
this.fieldParser.setHandler(createParsingHandler());
|
||||
this.descriptor = new HSDescriptor(hiddenService);
|
||||
this.authentication = new HSAuthentication(cookie);
|
||||
}
|
||||
|
||||
private DocumentParsingHandler createParsingHandler() {
|
||||
return new DocumentParsingHandler() {
|
||||
|
||||
public void parseKeywordLine() {
|
||||
processKeywordLine();
|
||||
}
|
||||
|
||||
public void endOfDocument() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean parse(DocumentParsingResultHandler<HSDescriptor> resultHandler) {
|
||||
this.resultHandler = resultHandler;
|
||||
fieldParser.startSignedEntity();
|
||||
try {
|
||||
fieldParser.processDocument();
|
||||
return true;
|
||||
} catch(TorParsingException e) {
|
||||
resultHandler.parsingError(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public DocumentParsingResult<HSDescriptor> parse() {
|
||||
final BasicDocumentParsingResult<HSDescriptor> result = new BasicDocumentParsingResult<HSDescriptor>();
|
||||
parse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void processKeywordLine() {
|
||||
final HSDescriptorKeyword keyword = HSDescriptorKeyword.findKeyword(fieldParser.getCurrentKeyword());
|
||||
if(!keyword.equals(HSDescriptorKeyword.UNKNOWN_KEYWORD)) {
|
||||
processKeyword(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
private void processKeyword(HSDescriptorKeyword keyword) {
|
||||
switch(keyword) {
|
||||
case RENDEZVOUS_SERVICE_DESCRIPTOR:
|
||||
descriptor.setDescriptorId(fieldParser.parseBase32Digest());
|
||||
break;
|
||||
case VERSION:
|
||||
if(fieldParser.parseInteger() != 2) {
|
||||
throw new TorParsingException("Unexpected Descriptor version");
|
||||
}
|
||||
break;
|
||||
|
||||
case PERMANENT_KEY:
|
||||
descriptor.setPermanentKey(fieldParser.parsePublicKey());
|
||||
break;
|
||||
|
||||
case SECRET_ID_PART:
|
||||
descriptor.setSecretIdPart(fieldParser.parseBase32Digest());
|
||||
break;
|
||||
|
||||
case PUBLICATION_TIME:
|
||||
descriptor.setPublicationTime(fieldParser.parseTimestamp());
|
||||
break;
|
||||
|
||||
case PROTOCOL_VERSIONS:
|
||||
descriptor.setProtocolVersions(fieldParser.parseIntegerList());
|
||||
break;
|
||||
|
||||
case INTRODUCTION_POINTS:
|
||||
processIntroductionPoints();
|
||||
break;
|
||||
|
||||
case SIGNATURE:
|
||||
processSignature();
|
||||
break;
|
||||
case UNKNOWN_KEYWORD:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processIntroductionPoints() {
|
||||
final DocumentObject ob = fieldParser.parseObject();
|
||||
final ByteBuffer buffer = createIntroductionPointBuffer(ob);
|
||||
final IntroductionPointParser parser = new IntroductionPointParser(new DocumentFieldParserImpl(buffer));
|
||||
parser.parse(new DocumentParsingResultHandler<IntroductionPoint>() {
|
||||
|
||||
public void documentParsed(IntroductionPoint document) {
|
||||
logger.fine("adding intro point "+ document.getIdentity());
|
||||
descriptor.addIntroductionPoint(document);
|
||||
}
|
||||
|
||||
public void documentInvalid(IntroductionPoint document, String message) {
|
||||
logger.info("Invalid introduction point received");
|
||||
}
|
||||
|
||||
public void parsingError(String message) {
|
||||
logger.info("Error parsing introduction points: "+ message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ByteBuffer createIntroductionPointBuffer(DocumentObject ob) {
|
||||
final byte[] content = Base64.decode(ob.getContent(false));
|
||||
if(content[0] == 'i') {
|
||||
return ByteBuffer.wrap(content);
|
||||
} else {
|
||||
try {
|
||||
byte[] decrypted = authentication.decryptIntroductionPoints(content);
|
||||
return ByteBuffer.wrap(decrypted);
|
||||
} catch (HSAuthenticationException e) {
|
||||
throw new TorParsingException("Failed to decrypt introduction points: "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processSignature() {
|
||||
fieldParser.endSignedEntity();
|
||||
final TorSignature signature = fieldParser.parseSignature();
|
||||
if(!fieldParser.verifySignedEntity(descriptor.getPermanentKey(), signature)) {
|
||||
resultHandler.documentInvalid(descriptor, "Signature verification failed");
|
||||
fieldParser.logWarn("Signature failed for descriptor: "+ descriptor.getDescriptorId().toBase32());
|
||||
return;
|
||||
}
|
||||
resultHandler.documentParsed(descriptor);
|
||||
}
|
||||
}
|
116
src/java/com/subgraph/orchid/circuits/hs/HSDirectories.java
Normal file
116
src/java/com/subgraph/orchid/circuits/hs/HSDirectories.java
Normal file
@ -0,0 +1,116 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.ConsensusDocument;
|
||||
import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.Router;
|
||||
import com.subgraph.orchid.crypto.TorRandom;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
|
||||
public class HSDirectories {
|
||||
private final static int DIR_CLUSTER_SZ = 3;
|
||||
private final Directory directory;
|
||||
private final TorRandom random;
|
||||
private ConsensusDocument currentConsensus;
|
||||
private List<Router> hsDirectories;
|
||||
|
||||
HSDirectories(Directory directory) {
|
||||
this.directory = directory;
|
||||
this.hsDirectories = new ArrayList<Router>();
|
||||
this.random = new TorRandom();
|
||||
}
|
||||
|
||||
List<HSDescriptorDirectory> getDirectoriesForHiddenService(HiddenService hs) {
|
||||
final List<HSDescriptorDirectory> dirs = new ArrayList<HSDescriptorDirectory>(2 * DIR_CLUSTER_SZ);
|
||||
for(HexDigest id: hs.getAllCurrentDescriptorIds()) {
|
||||
for(Router r: getDirectoriesForDescriptorId(id)) {
|
||||
dirs.add(new HSDescriptorDirectory(id, r));
|
||||
}
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
private List<Router> getDirectoriesForDescriptorId(HexDigest descriptorId) {
|
||||
final String hexId = descriptorId.toString();
|
||||
refreshFromDirectory();
|
||||
final int idx = getIndexForDescriptorId(hexId);
|
||||
return selectDirectoriesAtIndex(idx);
|
||||
}
|
||||
|
||||
private int getIndexForDescriptorId(String hexId) {
|
||||
for(int i = 0; i < hsDirectories.size(); i++) {
|
||||
String routerId = getHexIdForIndex(i);
|
||||
if(routerId.compareTo(hexId) > 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String getHexIdForIndex(int idx) {
|
||||
final Router r = hsDirectories.get(idx);
|
||||
return r.getIdentityHash().toString();
|
||||
}
|
||||
|
||||
private List<Router> selectDirectoriesAtIndex(int idx) {
|
||||
if(idx < 0 || idx >= hsDirectories.size()) {
|
||||
throw new IllegalArgumentException("idx = "+ idx);
|
||||
}
|
||||
if(hsDirectories.size() < DIR_CLUSTER_SZ) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
final List<Router> dirs = new ArrayList<Router>(DIR_CLUSTER_SZ);
|
||||
for(int i = 0; i < DIR_CLUSTER_SZ; i++) {
|
||||
dirs.add(hsDirectories.get(idx));
|
||||
idx += 1;
|
||||
if(idx == hsDirectories.size()) {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
randomShuffle(dirs);
|
||||
return dirs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void refreshFromDirectory() {
|
||||
ConsensusDocument consensus = directory.getCurrentConsensusDocument();
|
||||
if(currentConsensus == consensus) {
|
||||
return;
|
||||
}
|
||||
currentConsensus = consensus;
|
||||
hsDirectories.clear();
|
||||
for(Router r: directory.getAllRouters()) {
|
||||
if(r.isHSDirectory()) {
|
||||
hsDirectories.add(r);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(hsDirectories, new Comparator<Router>() {
|
||||
public int compare(Router r1, Router r2) {
|
||||
final String s1 = r1.getIdentityHash().toString();
|
||||
final String s2 = r2.getIdentityHash().toString();
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void randomShuffle(List<Router> dirs) {
|
||||
for(int i = 0; i < dirs.size(); i++) {
|
||||
swap(dirs, i, random.nextInt(dirs.size()));
|
||||
}
|
||||
}
|
||||
|
||||
private void swap(List<Router> dirs, int idx1, int idx2) {
|
||||
if(idx1 != idx2) {
|
||||
final Router r1 = dirs.get(idx1);
|
||||
final Router r2 = dirs.get(idx2);
|
||||
dirs.set(idx1, r2);
|
||||
dirs.set(idx2, r1);
|
||||
}
|
||||
}
|
||||
}
|
139
src/java/com/subgraph/orchid/circuits/hs/HiddenService.java
Normal file
139
src/java/com/subgraph/orchid/circuits/hs/HiddenService.java
Normal file
@ -0,0 +1,139 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.subgraph.orchid.HiddenServiceCircuit;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie.CookieType;
|
||||
import com.subgraph.orchid.crypto.TorMessageDigest;
|
||||
import com.subgraph.orchid.data.Base32;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
|
||||
public class HiddenService {
|
||||
|
||||
private final TorConfig config;
|
||||
private final byte[] permanentId;
|
||||
|
||||
private HSDescriptor descriptor;
|
||||
private HiddenServiceCircuit circuit;
|
||||
|
||||
static byte[] decodeOnion(String onionAddress) {
|
||||
final int idx = onionAddress.indexOf(".onion");
|
||||
if(idx == -1) {
|
||||
return Base32.base32Decode(onionAddress);
|
||||
} else {
|
||||
return Base32.base32Decode(onionAddress.substring(0, idx));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HiddenService(TorConfig config, byte[] permanentId) {
|
||||
this.config = config;
|
||||
this.permanentId = permanentId;
|
||||
}
|
||||
|
||||
String getOnionAddressForLogging() {
|
||||
if(config.getSafeLogging()) {
|
||||
return "[scrubbed]";
|
||||
} else {
|
||||
return getOnionAddress();
|
||||
}
|
||||
}
|
||||
|
||||
String getOnionAddress() {
|
||||
return Base32.base32Encode(permanentId) + ".onion";
|
||||
}
|
||||
|
||||
boolean hasCurrentDescriptor() {
|
||||
return (descriptor != null && !descriptor.isExpired());
|
||||
}
|
||||
|
||||
HSDescriptor getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
void setDescriptor(HSDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
HiddenServiceCircuit getCircuit() {
|
||||
return circuit;
|
||||
}
|
||||
|
||||
void setCircuit(HiddenServiceCircuit circuit) {
|
||||
this.circuit = circuit;
|
||||
}
|
||||
|
||||
HSDescriptorCookie getAuthenticationCookie() {
|
||||
return config.getHidServAuth(getOnionAddress());
|
||||
}
|
||||
|
||||
List<HexDigest> getAllCurrentDescriptorIds() {
|
||||
final List<HexDigest> ids = new ArrayList<HexDigest>();
|
||||
ids.add(getCurrentDescriptorId(0));
|
||||
ids.add(getCurrentDescriptorId(1));
|
||||
return ids;
|
||||
}
|
||||
|
||||
HexDigest getCurrentDescriptorId(int replica) {
|
||||
final TorMessageDigest digest = new TorMessageDigest();
|
||||
digest.update(permanentId);
|
||||
digest.update(getCurrentSecretId(replica));
|
||||
return digest.getHexDigest();
|
||||
}
|
||||
|
||||
byte[] getCurrentSecretId(int replica) {
|
||||
final TorMessageDigest digest = new TorMessageDigest();
|
||||
digest.update(getCurrentTimePeriod());
|
||||
final HSDescriptorCookie cookie = getAuthenticationCookie();
|
||||
if(cookie != null && cookie.getType() == CookieType.COOKIE_STEALTH) {
|
||||
digest.update(cookie.getValue());
|
||||
}
|
||||
digest.update(new byte[] { (byte) replica });
|
||||
return digest.getDigestBytes();
|
||||
}
|
||||
|
||||
byte[] getCurrentTimePeriod() {
|
||||
final long now = System.currentTimeMillis() / 1000;
|
||||
final int idByte = permanentId[0] & 0xFF;
|
||||
return calculateTimePeriod(now, idByte);
|
||||
}
|
||||
|
||||
static byte[] calculateTimePeriod(long currentTime, int idByte) {
|
||||
final long t = (currentTime + (idByte * 86400L / 256)) / 86400L;
|
||||
return toNetworkBytes(t);
|
||||
}
|
||||
|
||||
static byte[] toNetworkBytes(long value) {
|
||||
final byte[] result = new byte[4];
|
||||
for(int i = 3; i >= 0; i--) {
|
||||
result[i] = (byte) (value & 0xFF);
|
||||
value >>= 8;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(permanentId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
HiddenService other = (HiddenService) obj;
|
||||
if (!Arrays.equals(permanentId, other.permanentId))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.subgraph.orchid.Directory;
|
||||
import com.subgraph.orchid.HiddenServiceCircuit;
|
||||
import com.subgraph.orchid.OpenFailedException;
|
||||
import com.subgraph.orchid.Stream;
|
||||
import com.subgraph.orchid.StreamConnectFailedException;
|
||||
import com.subgraph.orchid.TorConfig;
|
||||
import com.subgraph.orchid.TorException;
|
||||
import com.subgraph.orchid.circuits.CircuitManagerImpl;
|
||||
|
||||
public class HiddenServiceManager {
|
||||
private final static int RENDEZVOUS_RETRY_COUNT = 5;
|
||||
private final static int HS_STREAM_TIMEOUT = 20000;
|
||||
|
||||
private final static Logger logger = Logger.getLogger(HiddenServiceManager.class.getName());
|
||||
|
||||
private final Map<String, HiddenService> hiddenServices;
|
||||
private final TorConfig config;
|
||||
private final Directory directory;
|
||||
private final HSDirectories hsDirectories;
|
||||
private final CircuitManagerImpl circuitManager;
|
||||
|
||||
public HiddenServiceManager(TorConfig config, Directory directory, CircuitManagerImpl circuitManager) {
|
||||
this.config = config;
|
||||
this.directory = directory;
|
||||
this.hiddenServices = new HashMap<String, HiddenService>();
|
||||
this.hsDirectories = new HSDirectories(directory);
|
||||
this.circuitManager = circuitManager;
|
||||
}
|
||||
|
||||
public Stream getStreamTo(String onion, int port) throws OpenFailedException, InterruptedException, TimeoutException {
|
||||
final HiddenService hs = getHiddenServiceForOnion(onion);
|
||||
final HiddenServiceCircuit circuit = getCircuitTo(hs);
|
||||
|
||||
try {
|
||||
return circuit.openStream(port, HS_STREAM_TIMEOUT);
|
||||
} catch (StreamConnectFailedException e) {
|
||||
throw new OpenFailedException("Failed to open stream to hidden service "+ hs.getOnionAddressForLogging() + " reason "+ e.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized HiddenServiceCircuit getCircuitTo(HiddenService hs) throws OpenFailedException {
|
||||
if(hs.getCircuit() == null) {
|
||||
final HiddenServiceCircuit c = openCircuitTo(hs);
|
||||
if(c == null) {
|
||||
throw new OpenFailedException("Failed to open circuit to "+ hs.getOnionAddressForLogging());
|
||||
}
|
||||
hs.setCircuit(c);
|
||||
}
|
||||
return hs.getCircuit();
|
||||
}
|
||||
|
||||
private HiddenServiceCircuit openCircuitTo(HiddenService hs) throws OpenFailedException {
|
||||
HSDescriptor descriptor = getDescriptorFor(hs);
|
||||
|
||||
for(int i = 0; i < RENDEZVOUS_RETRY_COUNT; i++) {
|
||||
final HiddenServiceCircuit c = openRendezvousCircuit(hs, descriptor);
|
||||
if(c != null) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
throw new OpenFailedException("Failed to open circuit to "+ hs.getOnionAddressForLogging());
|
||||
}
|
||||
|
||||
HSDescriptor getDescriptorFor(HiddenService hs) throws OpenFailedException {
|
||||
if(hs.hasCurrentDescriptor()) {
|
||||
return hs.getDescriptor();
|
||||
}
|
||||
final HSDescriptor descriptor = downloadDescriptorFor(hs);
|
||||
if(descriptor == null) {
|
||||
final String msg = "Failed to download HS descriptor for "+ hs.getOnionAddressForLogging();
|
||||
logger.info(msg);
|
||||
throw new OpenFailedException(msg);
|
||||
}
|
||||
hs.setDescriptor(descriptor);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private HSDescriptor downloadDescriptorFor(HiddenService hs) {
|
||||
logger.fine("Downloading HS descriptor for "+ hs.getOnionAddressForLogging());
|
||||
final List<HSDescriptorDirectory> dirs = hsDirectories.getDirectoriesForHiddenService(hs);
|
||||
final HSDescriptorDownloader downloader = new HSDescriptorDownloader(hs, circuitManager, dirs);
|
||||
return downloader.downloadDescriptor();
|
||||
}
|
||||
|
||||
HiddenService getHiddenServiceForOnion(String onion) throws OpenFailedException {
|
||||
final String key = onion.endsWith(".onion") ? onion.substring(0, onion.length() - 6) : onion;
|
||||
synchronized(hiddenServices) {
|
||||
if(!hiddenServices.containsKey(key)) {
|
||||
hiddenServices.put(key, createHiddenServiceFor(key));
|
||||
}
|
||||
return hiddenServices.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
private HiddenService createHiddenServiceFor(String key) throws OpenFailedException {
|
||||
try {
|
||||
byte[] decoded = HiddenService.decodeOnion(key);
|
||||
return new HiddenService(config, decoded);
|
||||
} catch (TorException e) {
|
||||
final String target = config.getSafeLogging() ? "[scrubbed]" : (key + ".onion");
|
||||
throw new OpenFailedException("Failed to decode onion address "+ target + " : "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private HiddenServiceCircuit openRendezvousCircuit(HiddenService hs, HSDescriptor descriptor) {
|
||||
final RendezvousCircuitBuilder builder = new RendezvousCircuitBuilder(directory, circuitManager, hs, descriptor);
|
||||
try {
|
||||
return builder.call();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import com.subgraph.orchid.crypto.TorPublicKey;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.data.IPv4Address;
|
||||
|
||||
public class IntroductionPoint {
|
||||
|
||||
private HexDigest identity;
|
||||
private IPv4Address address;
|
||||
private int onionPort;
|
||||
private TorPublicKey onionKey;
|
||||
private TorPublicKey serviceKey;
|
||||
|
||||
IntroductionPoint(HexDigest identity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
void setAddress(IPv4Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
void setOnionPort(int onionPort) {
|
||||
this.onionPort = onionPort;
|
||||
}
|
||||
|
||||
void setOnionKey(TorPublicKey onionKey) {
|
||||
this.onionKey = onionKey;
|
||||
}
|
||||
|
||||
void setServiceKey(TorPublicKey serviceKey) {
|
||||
this.serviceKey = serviceKey;
|
||||
}
|
||||
|
||||
boolean isValidDocument() {
|
||||
return identity != null && address != null && onionPort != 0 && onionKey != null && serviceKey != null;
|
||||
}
|
||||
|
||||
public HexDigest getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public IPv4Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return onionPort;
|
||||
}
|
||||
|
||||
public TorPublicKey getOnionKey() {
|
||||
return onionKey;
|
||||
}
|
||||
|
||||
public TorPublicKey getServiceKey() {
|
||||
return serviceKey;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
public enum IntroductionPointKeyword {
|
||||
SERVICE_AUTHENTICATION("service-authentication", 2),
|
||||
INTRODUCTION_POINT("introduction-point", 1),
|
||||
IP_ADDRESS("ip-address", 1),
|
||||
ONION_PORT("onion-port", 1),
|
||||
ONION_KEY("onion-key", 0),
|
||||
SERVICE_KEY("service-key", 0),
|
||||
INTRO_AUTHENTICATION("intro-authentication", 2),
|
||||
UNKNOWN_KEYWORD("KEYWORD NOT FOUND", 0);
|
||||
|
||||
private final String keyword;
|
||||
private final int argumentCount;
|
||||
|
||||
IntroductionPointKeyword(String keyword, int argumentCount) {
|
||||
this.keyword = keyword;
|
||||
this.argumentCount = argumentCount;
|
||||
}
|
||||
|
||||
String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
int getArgumentCount() {
|
||||
return argumentCount;
|
||||
}
|
||||
|
||||
static IntroductionPointKeyword findKeyword(String keyword) {
|
||||
for(IntroductionPointKeyword k: values()) {
|
||||
if(k.getKeyword().equals(keyword)) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return UNKNOWN_KEYWORD;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package com.subgraph.orchid.circuits.hs;
|
||||
|
||||
import com.subgraph.orchid.TorParsingException;
|
||||
import com.subgraph.orchid.data.HexDigest;
|
||||
import com.subgraph.orchid.directory.parsing.BasicDocumentParsingResult;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentFieldParser;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParser;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingHandler;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingResult;
|
||||
import com.subgraph.orchid.directory.parsing.DocumentParsingResultHandler;
|
||||
|
||||
public class IntroductionPointParser implements DocumentParser<IntroductionPoint>{
|
||||
|
||||
private final DocumentFieldParser fieldParser;
|
||||
|
||||
private DocumentParsingResultHandler<IntroductionPoint> resultHandler;
|
||||
private IntroductionPoint currentIntroductionPoint;
|
||||
|
||||
public IntroductionPointParser(DocumentFieldParser fieldParser) {
|
||||
this.fieldParser = fieldParser;
|
||||
this.fieldParser.setHandler(createParsingHandler());
|
||||
}
|
||||
|
||||
public boolean parse(DocumentParsingResultHandler<IntroductionPoint> resultHandler) {
|
||||
this.resultHandler = resultHandler;
|
||||
try {
|
||||
fieldParser.processDocument();
|
||||
return true;
|
||||
} catch(TorParsingException e) {
|
||||
resultHandler.parsingError(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public DocumentParsingResult<IntroductionPoint> parse() {
|
||||
final BasicDocumentParsingResult<IntroductionPoint> result = new BasicDocumentParsingResult<IntroductionPoint>();
|
||||
parse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private DocumentParsingHandler createParsingHandler() {
|
||||
return new DocumentParsingHandler() {
|
||||
public void parseKeywordLine() {
|
||||
processKeywordLine();
|
||||
}
|
||||
|
||||
public void endOfDocument() {
|
||||
validateAndReportIntroductionPoint(currentIntroductionPoint);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void resetIntroductionPoint(HexDigest identity) {
|
||||
validateAndReportIntroductionPoint(currentIntroductionPoint);
|
||||
currentIntroductionPoint = new IntroductionPoint(identity);
|
||||
}
|
||||
|
||||
private void validateAndReportIntroductionPoint(IntroductionPoint introductionPoint) {
|
||||
if(introductionPoint == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(introductionPoint.isValidDocument()) {
|
||||
resultHandler.documentParsed(introductionPoint);
|
||||
} else {
|
||||
resultHandler.documentInvalid(introductionPoint, "Invalid introduction point");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processKeywordLine() {
|
||||
final IntroductionPointKeyword keyword = IntroductionPointKeyword.findKeyword(fieldParser.getCurrentKeyword());
|
||||
if(!keyword.equals(IntroductionPointKeyword.UNKNOWN_KEYWORD)) {
|
||||
processKeyword(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
private void processKeyword(IntroductionPointKeyword keyword) {
|
||||
switch(keyword) {
|
||||
case INTRO_AUTHENTICATION:
|
||||
break;
|
||||
|
||||
case INTRODUCTION_POINT:
|
||||
resetIntroductionPoint(fieldParser.parseBase32Digest());
|
||||
break;
|
||||
|
||||
case IP_ADDRESS:
|
||||
if(currentIntroductionPoint != null) {
|
||||
currentIntroductionPoint.setAddress(fieldParser.parseAddress());
|
||||
}
|
||||
break;
|
||||
|
||||
case ONION_KEY:
|
||||
if(currentIntroductionPoint != null) {
|
||||
currentIntroductionPoint.setOnionKey(fieldParser.parsePublicKey());
|
||||
}
|
||||
break;
|
||||
|
||||
case ONION_PORT:
|
||||
if(currentIntroductionPoint != null) {
|
||||
currentIntroductionPoint.setOnionPort(fieldParser.parsePort());
|
||||
}
|
||||
break;
|
||||
|
||||
case SERVICE_KEY:
|
||||
if(currentIntroductionPoint != null) {
|
||||
currentIntroductionPoint.setServiceKey(fieldParser.parsePublicKey());
|
||||
}
|
||||
break;
|
||||
|
||||
case SERVICE_AUTHENTICATION:
|
||||
break;
|
||||
|
||||
case UNKNOWN_KEYWORD:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user