mirror of
https://github.com/opnsense/plugins.git
synced 2026-06-08 00:04:42 -04:00
Iperf3 (#396)
This commit is contained in:
parent
2a8c02ecfe
commit
67fc8db0e8
15 changed files with 741 additions and 0 deletions
8
net/iperf/Makefile
Normal file
8
net/iperf/Makefile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
PLUGIN_NAME= iperf3
|
||||
PLUGIN_VERSION= 0.0.1
|
||||
PLUGIN_COMMENT= iperf3 connection speed tester
|
||||
PLUGIN_DEPENDS= iperf3 ruby
|
||||
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
|
||||
PLUGIN_DEVEL= yes
|
||||
|
||||
.include "../../Mk/plugins.mk"
|
||||
11
net/iperf/pkg-descr
Normal file
11
net/iperf/pkg-descr
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
iperf3 is a tool for measuring the achievable TCP, UDP, and SCTP
|
||||
throughput along a path between two hosts. It allows the tuning of
|
||||
various parameters such as socket buffer sizes and maximum attempted
|
||||
throughput. It reports (among other things) bandwidth, delay jitter,
|
||||
and datagram loss. iperf was originally developed by NLANR/DAST.
|
||||
|
||||
iperf3 is a new implementation developed from scratch at the Energy
|
||||
Sciences Network (ESnet). Among its goals were a smaller, simpler
|
||||
code base (compared to its predecessor, iperf2) and a library version
|
||||
of the functionality that can be used in other programs. Note that
|
||||
iperf3 does not interoperate with with iperf 2.x.
|
||||
50
net/iperf/src/etc/inc/plugins.inc.d/iperf.inc
Normal file
50
net/iperf/src/etc/inc/plugins.inc.d/iperf.inc
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
function iperf_firewall($fw)
|
||||
{
|
||||
$fw->registerAnchor('iperf', 'fw');
|
||||
}
|
||||
|
||||
function iperf_services()
|
||||
{
|
||||
$services = array();
|
||||
|
||||
$services[] = array(
|
||||
'description' => gettext('iperf Performance Test'),
|
||||
'configd' => array(
|
||||
'restart' => array('iperf restart'),
|
||||
'start' => array('iperf start'),
|
||||
'stop' => array('iperf stop'),
|
||||
),
|
||||
'name' => 'iperf',
|
||||
'pidfile' => '/var/run/iperf.pid'
|
||||
);
|
||||
return $services;
|
||||
}
|
||||
13
net/iperf/src/etc/rc.d/iperf
Executable file
13
net/iperf/src/etc/rc.d/iperf
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
# REQUIRE: LOGIN DAEMON
|
||||
. /etc/rc.subr
|
||||
name=iperf
|
||||
rcvar=iperf_enable
|
||||
command_interpreter=/usr/local/bin/ruby
|
||||
command=/usr/local/opnsense/scripts/iperf/ruby_iperf.rb
|
||||
iperf_user=root
|
||||
iperf_pidfile=/var/run/iperf.pid
|
||||
start_cmd="/usr/sbin/daemon -u $iperf_user -p $iperf_pidfile -f $command"
|
||||
load_rc_config $name
|
||||
: ${iperf_enable="YES"}
|
||||
run_rc_command "$1"
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OPNsense\iperf\Api;
|
||||
|
||||
use \OPNsense\Base\ApiMutableModelControllerBase;
|
||||
use \OPNsense\Core\Backend;
|
||||
use \OPNsense\Core\Config;
|
||||
use \OPNsense\Iperf\FakeInstance;
|
||||
|
||||
class InstanceController extends ApiMutableModelControllerBase
|
||||
{
|
||||
static protected $internalModelClass = '\OPNsense\iperf\FakeInstance';
|
||||
static protected $internalModelName = 'instance';
|
||||
static private $SOCKET_PATH = "unix:///var/run/iperf-manager.sock";
|
||||
|
||||
// override base to set model - not used here
|
||||
public function setAction() {
|
||||
$backend = new Backend();
|
||||
|
||||
// if no socket file exist, we know that the service is not running
|
||||
if (!file_exists("/var/run/iperf-manager.sock")) {
|
||||
$backend->configdRun('iperf start');
|
||||
}
|
||||
if (!isset($_POST['instance']['interface'])) {
|
||||
return array('status' => 'error',
|
||||
'error' => 'interface parameter is missing');
|
||||
}
|
||||
$interface_name = $_POST['instance']['interface'];
|
||||
if ($interface = $this->get_real_interface_name($interface_name)) {
|
||||
// start iperf
|
||||
return $this->send_command("start $interface", $backend);
|
||||
} else {
|
||||
return array('status' => 'error',
|
||||
'error' => 'interface is unknown');
|
||||
}
|
||||
}
|
||||
|
||||
public function queryAction() {
|
||||
$backend = new Backend();
|
||||
return $this->send_command('query', $backend);
|
||||
}
|
||||
|
||||
private function send_command($command, $backend) {
|
||||
try {
|
||||
$socket = @stream_socket_client(InstanceController::$SOCKET_PATH, $error_code, $error_msg);}
|
||||
catch (\Exception $e) {
|
||||
$socket = null;
|
||||
}
|
||||
if (!$socket) {
|
||||
// in case of an error: try to restart the service and if that fails too
|
||||
// don't retry anymore
|
||||
$backend->configdRun('iperf restart');
|
||||
$socket = @stream_socket_client(InstanceController::$SOCKET_PATH, $error_code, $error_msg);
|
||||
if (!$socket) {
|
||||
return array('state' => 'error', 'code' => $error_code, 'msg' => $error_msg);
|
||||
}
|
||||
}
|
||||
fwrite($socket, "$command\n");
|
||||
$data = fgets($socket);
|
||||
fwrite($socket, "bye\n");
|
||||
fgets($socket);
|
||||
fclose($socket);
|
||||
return json_decode($data,true);
|
||||
|
||||
}
|
||||
private function get_real_interface_name($name) {
|
||||
$config = Config::getInstance()->toArray();
|
||||
if (isset($config['interfaces'][$name])) {
|
||||
return $config['interfaces'][$name]['if'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace OPNsense\iperf\Api;
|
||||
|
||||
use \OPNsense\Base\ApiControllerBase;
|
||||
use \OPNsense\Core\Backend;
|
||||
|
||||
class ServiceController extends ApiControllerBase
|
||||
{
|
||||
public function statusAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
$result = array('result' => 'failed');
|
||||
$res = $backend->configdRun('iperf status');
|
||||
if (stripos($res, 'is running')) {
|
||||
$result['result'] = 'running';
|
||||
} else {
|
||||
$result['result'] = 'stopped';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function startAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
$result = array('result' => $backend->configdRun('iperf start'));
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function stopAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
$result = array("result" => $backend->configdRun('iperf stop'));
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function restartAction()
|
||||
{
|
||||
$this->stopAction();
|
||||
return $this->startAction();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/*
|
||||
|
||||
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.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
namespace OPNsense\iperf;
|
||||
|
||||
/**
|
||||
* Class IndexController
|
||||
* @package OPNsense/Iperf
|
||||
*/
|
||||
class IndexController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->instance_settings = $this->getForm("instance_settings");
|
||||
$this->view->pick('OPNsense/iperf/index');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<form>
|
||||
<field>
|
||||
<id>instance.interface</id>
|
||||
<label>Interface</label>
|
||||
<type>dropdown</type>
|
||||
<multiple>N</multiple>
|
||||
<help>Choose the interface on which the port should be opened.</help>
|
||||
</field>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<acl>
|
||||
<page-Iperf>
|
||||
<name>iperf</name>
|
||||
<patterns>
|
||||
<pattern>ui/iperf/*</pattern>
|
||||
<pattern>api/iperf/*</pattern>
|
||||
</patterns>
|
||||
</page-Iperf>
|
||||
</acl>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OPNsense\iperf;
|
||||
|
||||
use OPNsense\Base\BaseModel;
|
||||
|
||||
class FakeInstance extends BaseModel
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<model>
|
||||
<mount>//OPNsense/Iperf3</mount>
|
||||
<description>Fake model for the API - will be never stored to config (only used for defaults, validation etc.).</description>
|
||||
<items>
|
||||
<interface type="InterfaceField">
|
||||
<default>lan</default>
|
||||
<Required>Y</Required>
|
||||
<multiple>N</multiple>
|
||||
</interface>
|
||||
</items>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<menu>
|
||||
<Interfaces>
|
||||
<Diagnostics>
|
||||
<Iperf url="/ui/iperf/" />
|
||||
</Diagnostics>
|
||||
</Interfaces>
|
||||
</menu>
|
||||
138
net/iperf/src/opnsense/mvc/app/views/OPNsense/iperf/index.volt
Normal file
138
net/iperf/src/opnsense/mvc/app/views/OPNsense/iperf/index.volt
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
{#
|
||||
|
||||
Copyright © 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 table_tr_kv(key, value) {
|
||||
return "<tr><td>" + key + "</td><td>" + value + "</td></tr>";
|
||||
}
|
||||
function table_tr_transpose(key, key_name, list, arr) {
|
||||
data = "<tr><td>" + key + "</td>";
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
data += "<td>" + arr[list[i]][key_name] + "</td>";
|
||||
}
|
||||
data += "</tr>";
|
||||
return data;
|
||||
}
|
||||
|
||||
function result_to_html(elements) {
|
||||
var output = '';
|
||||
for (var element_cnt = 0; element_cnt < elements.length; element_cnt++) {
|
||||
var element = elements[element_cnt];
|
||||
output += "{{ lang._('%sResult %s%s') }}".replace('%s','<h2>').replace('%s',element_cnt + 1).replace('%s','</h2>');
|
||||
output += '<table class="table table-striped"><tr><td>{{ lang._('Interface') }}</td><td>' + element.interface + '</td></tr>' +
|
||||
'<tr><td>{{ lang._('Start Time') }}</td><td>' + element.start_time + '</td></tr>' +
|
||||
'<tr><td>{{ lang._('Port') }}</td><td>' + element.port + '</td></tr></table>';
|
||||
|
||||
// only if test did already run
|
||||
if ('result' in element) {
|
||||
var result = element.result,
|
||||
start = result.start,
|
||||
connection = start.connected[0],
|
||||
intervals = result.intervals,
|
||||
test_end = result.end,
|
||||
cpu = test_end.cpu_utilization_percent;
|
||||
// General
|
||||
output += "<h3>{{ lang._('General') }}</h3>";
|
||||
output += '<table class="table table-striped">';
|
||||
output += table_tr_kv("{{ lang._('Time') }}", start.timestamp.time);
|
||||
output += table_tr_kv("{{ lang._('Duration') }}", start.test_start.duration);
|
||||
output += table_tr_kv("{{ lang._('Block Size') }}", start.test_start.blksize);
|
||||
output += "</table>";
|
||||
// connection
|
||||
output += "<h3>{{ lang._('Connection') }}</h3>";
|
||||
output += '<table class="table table-striped">';
|
||||
output += table_tr_kv("{{ lang._('Local Host') }}", connection.local_host);
|
||||
output += table_tr_kv("{{ lang._('Local Port') }}", connection.local_port);
|
||||
output += table_tr_kv("{{ lang._('Remote Host') }}", connection.remote_host);
|
||||
output += table_tr_kv("{{ lang._('Remote Port') }}", connection.remote_port);
|
||||
output += "</table>";
|
||||
// CPU Usage
|
||||
output += "<h3>{{ lang._('CPU Usage') }}</h3>";
|
||||
output += '<table class="table table-striped">';
|
||||
output += table_tr_kv("{{ lang._('Host Total') }}", cpu.host_total.toFixed(2));
|
||||
output += table_tr_kv("{{ lang._('Host User') }}", cpu.host_user.toFixed(2));
|
||||
output += table_tr_kv("{{ lang._('Host System') }}", cpu.host_system.toFixed(2));
|
||||
output += table_tr_kv("{{ lang._('Remote Total') }}", cpu.remote_total.toFixed(2));
|
||||
output += table_tr_kv("{{ lang._('Remote User') }}", cpu.remote_user.toFixed(2));
|
||||
output += table_tr_kv("{{ lang._('Remote System') }}", cpu.remote_system.toFixed(2));
|
||||
output += "</table>";
|
||||
// performance data
|
||||
output += "<h3>{{ lang._('Performance Data') }}</h3>";
|
||||
output += '<table class="table table-striped">';
|
||||
var fields = ['sum_sent', 'sum_received'];
|
||||
output += table_tr_transpose("{{ lang._('Start') }}","start",fields, test_end);
|
||||
output += table_tr_transpose("{{ lang._('End') }}","end",fields, test_end);
|
||||
output += table_tr_transpose("{{ lang._('Seconds') }}","seconds",fields, test_end);
|
||||
output += table_tr_transpose("{{ lang._('Bytes') }}","bytes",fields, test_end);
|
||||
output += table_tr_transpose("{{ lang._('Bits Per Second') }}","bits_per_second",fields, test_end);
|
||||
output += "</table>";
|
||||
}
|
||||
}
|
||||
$('#resultcontainer').html(output);
|
||||
}
|
||||
|
||||
function update_results() {
|
||||
ajaxCall(url="/api/iperf/instance/query", sendData={}, callback=function(data,status) {
|
||||
result_to_html(data);
|
||||
});
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
var data_get_map = {'instance': '/api/iperf/instance/get'};
|
||||
mapDataToFormUI(data_get_map).done(function(data){
|
||||
formatTokenizersUI();
|
||||
$('select').selectpicker('refresh');
|
||||
});
|
||||
|
||||
ajaxCall(url="/api/iperf/service/status", sendData={}, callback=function(data,status) {
|
||||
updateServiceStatusUI(data['result']);
|
||||
});
|
||||
update_results();
|
||||
setInterval(update_results, 10000);
|
||||
|
||||
// link save button to API set action
|
||||
$("#create_instance_action").click(function(){
|
||||
$("#create_instance_action_progress").addClass("fa fa-spinner fa-pulse");
|
||||
saveFormToEndpoint(url="/api/iperf/instance/set", formid='instance',callback_ok=function(){
|
||||
$("#create_instance_action_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div class="content-box" style="padding-bottom: 1.5em;">
|
||||
{{ partial("layout_partials/base_form",['fields': instance_settings,'id':'instance'])}}
|
||||
<div class="col-md-12">
|
||||
<hr />
|
||||
<button class="btn btn-primary" id="create_instance_action" type="button"><b>{{ lang._('Create Instance') }}</b> <i id="create_instance_action_progress"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resultcontainer" class="content-box"></div>
|
||||
211
net/iperf/src/opnsense/scripts/iperf/ruby_iperf.rb
Executable file
211
net/iperf/src/opnsense/scripts/iperf/ruby_iperf.rb
Executable file
|
|
@ -0,0 +1,211 @@
|
|||
#!/usr/local/bin/ruby
|
||||
|
||||
=begin
|
||||
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.
|
||||
=end
|
||||
|
||||
require 'open3'
|
||||
require 'pp'
|
||||
require 'json'
|
||||
require 'logger'
|
||||
require 'rexml/document'
|
||||
require 'timeout'
|
||||
require 'socket'
|
||||
|
||||
$instances = {}
|
||||
SOCKET_FILE = '/var/run/iperf-manager.sock'
|
||||
ONE_HOUR = 3600
|
||||
KEY_START_TIME = 'start_time'
|
||||
KEY_PORT = 'port'
|
||||
LF = "\n"
|
||||
|
||||
def execute_firewall_port(rule)
|
||||
Open3.popen3('pfctl -a iperf -f -') do |stdin, stdout, stderr, wait_thr|
|
||||
stdin.puts rule
|
||||
stdin.close
|
||||
stdout.close
|
||||
stderr.close
|
||||
end
|
||||
end
|
||||
|
||||
def flush_firewall_rules
|
||||
`pfctl -a iperf -F rules`
|
||||
end
|
||||
|
||||
def create_open_firewall_port_rule(interface, port, log = 'log')
|
||||
"pass in #{log} quick on #{interface} inet proto tcp from {any} to {(self)} port {#{port}} keep state"
|
||||
end
|
||||
|
||||
def gen_firewall_rules
|
||||
rules = ''
|
||||
$instances.each do |thread, entry|
|
||||
pp (["thread alive?", thread.alive?, entry])
|
||||
if thread.alive?
|
||||
if entry.has_key?('interface') && entry.has_key?('port') && !entry.has_key?('result')
|
||||
rules << create_open_firewall_port_rule(entry['interface'], entry['port']) << LF
|
||||
end
|
||||
end
|
||||
end
|
||||
execute_firewall_port(rules) if rules.length > 0
|
||||
end
|
||||
|
||||
def open_ports
|
||||
`sockstat -l`.lines.map do |x|
|
||||
# scan for integers after :
|
||||
x.scan(/.*:(\d+).*/)&.first&.first&.to_i
|
||||
end.uniq.reject(&:nil?).sort
|
||||
end
|
||||
|
||||
def forwarded_ports
|
||||
config = REXML::Document.new(File.new("/conf/config.xml"))
|
||||
xml_firewall = config.elements['opnsense/nat'].children.select do |x|
|
||||
x.node_type == :element && x.name == 'rule'
|
||||
end
|
||||
xml_firewall.map do |x|
|
||||
x&.elements['local-port']&.text&.to_i
|
||||
end.sort.uniq
|
||||
|
||||
end
|
||||
|
||||
def find_open_ports
|
||||
ports = (1024..65000).to_a
|
||||
# remove the ports open by the firewall itself
|
||||
begin
|
||||
ports -= open_ports
|
||||
rescue
|
||||
print $!
|
||||
end
|
||||
# remove the nat ports
|
||||
begin
|
||||
ports -= forwarded_ports
|
||||
rescue
|
||||
print $!
|
||||
end
|
||||
ports
|
||||
end
|
||||
|
||||
def find_open_port
|
||||
find_open_ports.sample
|
||||
end
|
||||
|
||||
def run_iperf3(port)
|
||||
output = pid = exit_status = ''
|
||||
Open3.popen3(['iperf3', '-J', '-f', 'M', '-V', '-s', '-1', '-p', port].join ' ') do |stdin, stdout, stderr, wait_thr|
|
||||
pid = wait_thr.pid # pid of the started process.
|
||||
stdin.close
|
||||
exit_status = wait_thr.value
|
||||
output = JSON.parse(stdout.read)
|
||||
stdout.close
|
||||
stderr.close
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
def run_test(interface = 'any', data)
|
||||
ret = nil
|
||||
data[KEY_PORT] = port = find_open_port
|
||||
# regenerate ruleset
|
||||
flush_firewall_rules
|
||||
gen_firewall_rules
|
||||
# do perform test
|
||||
begin
|
||||
# timeout 10 min
|
||||
begin
|
||||
Timeout.timeout(600) do
|
||||
data['result'] = ret = run_iperf3 port
|
||||
end
|
||||
rescue Timeout::Error
|
||||
data['result'] = ret = [-1,{'error' => 'timeout'}]
|
||||
end
|
||||
rescue
|
||||
puts $!
|
||||
end
|
||||
# end perform test
|
||||
# regenerate ruleset
|
||||
flush_firewall_rules
|
||||
gen_firewall_rules
|
||||
ret
|
||||
end
|
||||
|
||||
def run_test_thread(interface = 'any')
|
||||
data = {}
|
||||
t = Thread.new do
|
||||
data[KEY_START_TIME] = Time.now
|
||||
data['interface'] = interface
|
||||
run_test(interface, data)
|
||||
end
|
||||
$instances[t] = data
|
||||
end
|
||||
|
||||
Thread.new do
|
||||
loop do
|
||||
$instances.each do |key, value|
|
||||
current_time = Time.now
|
||||
if (current_time - value[KEY_START_TIME]) > ONE_HOUR
|
||||
$instances.delete(key)
|
||||
key.kill unless key.stop?
|
||||
end
|
||||
end
|
||||
sleep 10
|
||||
end
|
||||
end
|
||||
|
||||
# delete stale socket file
|
||||
File.unlink(SOCKET_FILE) if File.exist? SOCKET_FILE
|
||||
|
||||
server = UNIXServer.new(SOCKET_FILE)
|
||||
begin
|
||||
loop do
|
||||
Thread.start(server.accept) do |connection|
|
||||
until connection.closed?
|
||||
begin
|
||||
command = connection.gets.strip.split(' ')
|
||||
case command.shift
|
||||
when 'start'
|
||||
interface = 'any'
|
||||
if command.length > 0
|
||||
intf = command.shift
|
||||
# check if a valid interface was given
|
||||
interface = intf if intf =~ /^[a-z0-9_-]+$/
|
||||
end
|
||||
data = run_test_thread interface
|
||||
connection.puts '{"status": "queued job"}'
|
||||
when 'query'
|
||||
connection.puts $instances.values.to_json
|
||||
when 'bye'
|
||||
connection.puts '{"status": "disconnecting"}'
|
||||
connection.close
|
||||
else
|
||||
connection.puts '{"status": "unknown command"}'
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
server.close
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
[start]
|
||||
command:/usr/local/etc/rc.d/iperf start
|
||||
parameters:
|
||||
type:script
|
||||
message:starting iperf daemon
|
||||
|
||||
[stop]
|
||||
command:/usr/local/etc/rc.d/iperf stop
|
||||
parameters:
|
||||
type:script
|
||||
message:stopping iperf daemon
|
||||
|
||||
[restart]
|
||||
command:/usr/local/etc/rc.d/iperf restart
|
||||
parameters:
|
||||
type:script
|
||||
message:restarting iperf daemon
|
||||
|
||||
[status]
|
||||
command:/usr/local/etc/rc.d/iperf status
|
||||
parameters:
|
||||
type:script_output
|
||||
message:request iperf daemon status
|
||||
Loading…
Reference in a new issue