This commit is contained in:
Fabian Franz, BSc 2017-12-01 21:40:40 +01:00 committed by GitHub
parent 2a8c02ecfe
commit 67fc8db0e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 741 additions and 0 deletions

8
net/iperf/Makefile Normal file
View 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
View 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.

View 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
View 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"

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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');
}
}

View file

@ -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>

View file

@ -0,0 +1,9 @@
<acl>
<page-Iperf>
<name>iperf</name>
<patterns>
<pattern>ui/iperf/*</pattern>
<pattern>api/iperf/*</pattern>
</patterns>
</page-Iperf>
</acl>

View file

@ -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
{
}

View file

@ -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>

View file

@ -0,0 +1,7 @@
<menu>
<Interfaces>
<Diagnostics>
<Iperf url="/ui/iperf/" />
</Diagnostics>
</Interfaces>
</menu>

View 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>

View 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

View file

@ -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