mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
Tor: add a diagnostics page (#329)
This commit is contained in:
committed by
Franco Fichtner
parent
e8d46fa119
commit
9b2e5a686f
@@ -1,7 +1,7 @@
|
||||
PLUGIN_NAME= tor
|
||||
PLUGIN_VERSION= 1.1
|
||||
PLUGIN_VERSION= 1.2
|
||||
PLUGIN_COMMENT= The Onion Router
|
||||
PLUGIN_DEPENDS= tor
|
||||
PLUGIN_DEPENDS= tor ruby
|
||||
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
|
||||
|
||||
.include "../../Mk/plugins.mk"
|
||||
|
||||
@@ -157,4 +157,24 @@ class ServiceController extends ApiControllerBase
|
||||
return array('status' => 'failed');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* query tor circuits
|
||||
* @return array
|
||||
*/
|
||||
public function circuitsAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
$response = json_decode($backend->configdRun('tor circuit'));
|
||||
return array('response' => $response);
|
||||
}
|
||||
/**
|
||||
* query tor streams
|
||||
* @return array
|
||||
*/
|
||||
public function streamsAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
$response = json_decode($backend->configdRun('tor streams'));
|
||||
return array('response' => $response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,19 @@ class IndexController extends \OPNsense\Base\IndexController
|
||||
$this->view->title = gettext("The Onion Router - Information");
|
||||
$this->view->pick('OPNsense/Tor/info');
|
||||
}
|
||||
public function diagnosticsAction()
|
||||
{
|
||||
$this->view->title = gettext("The Onion Router - Diagnostics");
|
||||
if ($this->is_tor_running()) {
|
||||
$this->view->pick('OPNsense/Tor/diagnostics');
|
||||
}
|
||||
else {
|
||||
$this->view->pick('OPNsense/Tor/error');
|
||||
}
|
||||
}
|
||||
private function is_tor_running()
|
||||
{
|
||||
$status = (new Api\ServiceController())->statusAction();
|
||||
return $status['status'] == 'running';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<menu>
|
||||
<Services>
|
||||
<tor VisibleName="Tor" cssClass="fa fa-bolt fa-fw">
|
||||
<configuration VisibleName="Configuration" url="/ui/tor/" />
|
||||
<info VisibleName="Information" url="/ui/tor/index/info" />
|
||||
</tor>
|
||||
<Tor VisibleName="Tor" cssClass="fa fa-bolt fa-fw">
|
||||
<Configuration url="/ui/tor/" />
|
||||
<Information url="/ui/tor/index/info" />
|
||||
<Diagnostics url="/ui/tor/index/diagnostics" />
|
||||
</Tor>
|
||||
</Services>
|
||||
</menu>
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
{#
|
||||
|
||||
Copyright (C) 2017 Fabian Franz
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#}
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function tor_update_status() {
|
||||
ajaxCall(url='/api/tor/service/status', sendData={}, callback=function(data, status) {
|
||||
updateServiceStatusUI(data['status']);
|
||||
});
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
|
||||
tor_update_status();
|
||||
ajaxCall(url='/api/tor/service/circuits', sendData={}, callback=function(data, status) {
|
||||
data = data['response'];
|
||||
var tmp = '';
|
||||
for (var name in data) {
|
||||
if (data.hasOwnProperty(name)) {
|
||||
tmp += '<tr><td>' + name +
|
||||
'</td><td>' + data[name]['status'] +
|
||||
'</td><td><ul>';
|
||||
hosts = data[name]['hosts'];
|
||||
for (var host_id in hosts) {
|
||||
if (hosts.hasOwnProperty(host_id)) {
|
||||
tmp += '<li>' + hosts[host_id]['host'] + ' - ' + hosts[host_id]['nickname'] + '</li>';
|
||||
}
|
||||
}
|
||||
|
||||
tmp += '</ul></td><td><ul>'
|
||||
|
||||
flags = data[name]['flags'];
|
||||
for (var flag_id in flags) {
|
||||
if (flags.hasOwnProperty(flag_id)) {
|
||||
tmp += '<li>' + flag_id + ': ' + flags[flag_id].join(', ') + '</li>';
|
||||
}
|
||||
}
|
||||
tmp += '</ul></td></tr>';
|
||||
}
|
||||
}
|
||||
$("#circuitstbdy").html(tmp);
|
||||
});
|
||||
ajaxCall(url="/api/tor/service/streams", sendData={}, callback=function(data, status) {
|
||||
data = data['response'];
|
||||
var tmp = '';
|
||||
for (var name in data) {
|
||||
if (data.hasOwnProperty(name)) {
|
||||
tmp += '<tr><td>' + data[name]['stream_id'] +
|
||||
'</td><td>' + data[name]['stream_status'] +
|
||||
'</td><td>' + data[name]['circuit_id'] +
|
||||
'</td><td>' + data[name]['destination_host'] +
|
||||
'</td><td>' + data[name]['destination_port'] +
|
||||
'</td></tr>';
|
||||
}
|
||||
}
|
||||
$("#streamstbdy").html(tmp);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#streams">{{ lang._('Streams') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#circuits">{{ lang._('Circuits') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box tab-content" style="padding-bottom: 1.5em;">
|
||||
<div id="streams" class="tab-pane fade in active">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ lang._('Stream ID') }}</th>
|
||||
<th>{{ lang._('Stream Status') }}</th>
|
||||
<th>{{ lang._('Circuit ID') }}</th>
|
||||
<th>{{ lang._('Destination Host') }}</th>
|
||||
<th>{{ lang._('Destination Port') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="streamstbdy"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="circuits" class="tab-pane fade in">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ lang._('Circuit ID') }}</th>
|
||||
<th>{{ lang._('Status') }}</th>
|
||||
<th>{{ lang._('Hosts') }}</th>
|
||||
<th>{{ lang._('Flags') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="circuitstbdy"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
{#
|
||||
|
||||
Copyright (C) 2017 Fabian Franz
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#}
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{ lang._('This page is not available because Tor is not running. Please go to the %sconfiguration page%s and enable Tor.')|format('<a href="/ui/tor/">','</a>') }}
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@ $( document ).ready(function() {
|
||||
var tmp = '';
|
||||
for (var name in data) {
|
||||
if (data.hasOwnProperty(name)) {
|
||||
tmp += '<tr><td>' + name + '</td><td>' + data[name] + '</td></tr>';
|
||||
tmp += '<tr><td>' + name + '</td><td>' + data[name].replace("\n",'<br />') + '</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ $( document ).ready(function() {
|
||||
|
||||
<div class="tab-content content-box tab-content" style="padding-bottom: 1.5em;">
|
||||
<div id="hiddennames" class="tab-pane fade in active">
|
||||
<table style="margin: 10px;">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ lang._('Onion Service Name') }}</th>
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
#!/usr/local/bin/ruby
|
||||
=begin
|
||||
Copyright 2017 Fabian Franz
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
=end
|
||||
|
||||
|
||||
require 'enumerator'
|
||||
require 'json'
|
||||
require 'optparse'
|
||||
require 'pp'
|
||||
require 'socket'
|
||||
require 'rexml/document'
|
||||
|
||||
# global for showing debug output if needed
|
||||
$TOR_DEBUG = false
|
||||
|
||||
|
||||
config = REXML::Document.new(File.new("/conf/config.xml"))
|
||||
$TOR_PASSWORD = config.elements['opnsense/OPNsense/tor/general/control_port_password'].text
|
||||
|
||||
class TorCTL
|
||||
|
||||
def initialize
|
||||
@tor = TCPSocket.new("127.0.0.1", 9051)
|
||||
send_query("AUTHENTICATE \"#{$TOR_PASSWORD}\"")
|
||||
end
|
||||
|
||||
|
||||
# new ip: signal NEWNYM
|
||||
|
||||
def get_version
|
||||
{ version: send_query("GETINFO version") }
|
||||
end
|
||||
|
||||
def close
|
||||
@tor.puts("QUIT")
|
||||
@tor.close
|
||||
end
|
||||
|
||||
def send_query(str)
|
||||
@tor.puts(str)
|
||||
ret = ''
|
||||
while (data = @tor.gets&.strip) && (!data.start_with? "250 ")
|
||||
begin
|
||||
res = data.scan(/^(\d{3})(.*)$/).first
|
||||
if res[1][0] == '-'
|
||||
ret << res[1][1..-1] + "\n"
|
||||
else
|
||||
ret << res[1] + "\n"
|
||||
end
|
||||
rescue
|
||||
ret << data + "\n"
|
||||
end
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def get_config(str)
|
||||
send_query("getconf #{str}")
|
||||
end
|
||||
|
||||
def get_info(str)
|
||||
d = send_query("getinfo #{str}")
|
||||
lines = d.lines
|
||||
if lines.first.end_with? "=\n"
|
||||
lines.shift # property=
|
||||
lines.pop # .
|
||||
else
|
||||
lines[0] = lines[0][((lines[0].index "=") + 1)...(lines[0].length)]
|
||||
end
|
||||
lines.join
|
||||
end
|
||||
|
||||
def get_circuit
|
||||
lines = get_info('circuit-status').lines
|
||||
pp lines if $TOR_DEBUG
|
||||
lines.each(&:strip!)
|
||||
ret = {}
|
||||
lines.each do |line|
|
||||
data = line.split(' ')
|
||||
circuit_id = data.shift.to_i
|
||||
status = data.shift
|
||||
hosts = data.shift.split(',').map do |host_nickname|
|
||||
h,n = host_nickname.split('~')
|
||||
{ host: h, nickname: n }
|
||||
end
|
||||
flags = data.map do |flag_entry|
|
||||
flag_key, flag_value = flag_entry.split('=')
|
||||
[flag_key, flag_value.split(',')]
|
||||
end.to_h
|
||||
|
||||
ret[circuit_id] = { status: status,
|
||||
hosts: hosts,
|
||||
flags: flags }
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def stream_status
|
||||
stream_status_data = get_info("stream-status")
|
||||
result = []
|
||||
stream_status_data.split(' ').each_slice(4) do |data_set|
|
||||
d_ip, d_port = data_set[3].scan(/(.*):(\d+)/).first
|
||||
result << { stream_id: data_set[0],
|
||||
stream_status: data_set[1],
|
||||
circuit_id: data_set[2],
|
||||
destination_host: d_ip,
|
||||
destination_port: d_port
|
||||
}
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
options = {}
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: #{__FILE__} ARGS"
|
||||
opts.on("-s", "--stream_status", "Print stream status") do |od|
|
||||
options[:action] = :stream_status
|
||||
end
|
||||
opts.on("-c", "--circuit", 'Print the Circuit') do |od|
|
||||
options[:action] = :get_circuit
|
||||
end
|
||||
opts.on("-v", "--version", 'Print the Tor version') do |od|
|
||||
options[:action] = :get_version
|
||||
end
|
||||
opts.on("-x", "--debug", "Prints debug output") do |od|
|
||||
$TOR_DEBUG = true
|
||||
end
|
||||
opts.on("-h", "--help", "Prints this help") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end.parse!
|
||||
|
||||
tor = TorCTL.new
|
||||
|
||||
no_param_actions = [:get_circuit, :stream_status, :get_version]
|
||||
if no_param_actions.include? options[:action]
|
||||
begin
|
||||
data = tor.send(options[:action])
|
||||
if data
|
||||
puts data.to_json
|
||||
else
|
||||
puts error: "no data"
|
||||
end
|
||||
rescue
|
||||
puts error: "execution error", error_str: $!
|
||||
end
|
||||
end
|
||||
|
||||
#puts tor.get_config("controlport")
|
||||
tor.close
|
||||
|
||||
|
||||
@@ -33,3 +33,15 @@ command:/usr/local/opnsense/scripts/tor/get_hostnames
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Query hostnames of Onion services
|
||||
|
||||
[circuit]
|
||||
command:/usr/local/opnsense/scripts/tor/tor_diag -c
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Query Tor circuit
|
||||
|
||||
[streams]
|
||||
command:/usr/local/opnsense/scripts/tor/tor_diag -s
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Query Tor stream information
|
||||
|
||||
Reference in New Issue
Block a user