Interfaces: Diagnostics: Trace Route: migrate to MVC, closes https://github.com/opnsense/core/issues/6399

This commit is contained in:
Ad Schellevis 2023-04-15 19:03:24 +02:00
parent 2ff2989f72
commit cfd70c71b1
11 changed files with 392 additions and 188 deletions

View File

@ -0,0 +1,55 @@
<?php
/**
* Copyright (C) 2023 Deciso B.V.
* 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\Diagnostics\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
class TracerouteController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'traceroute';
protected static $internalModelClass = 'OPNsense\Diagnostics\Traceroute';
public function setAction()
{
$result = parent::setAction();
if (empty($result['validations'])) {
// field validation passed, execute query
$mdl = $this->getModel();
$result['result'] = 'ok';
$result['response'] = json_decode((new Backend())->configdpRun('interface traceroute', [
(string)$mdl->settings->hostname,
(string)$mdl->settings->ipproto,
(string)$mdl->settings->source_address,
(string)$mdl->settings->protocol
]));
}
return $result;
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Copyright (C) 2023 Deciso B.V.
*
* 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\Diagnostics;
use OPNsense\Base\IndexController;
/**
* Class TracerouteController
* @package OPNsense\Diagnostics
*/
class TracerouteController extends IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Diagnostics/traceroute');
$this->view->tracerouteForm = $this->getForm("traceroute");
}
}

View File

@ -0,0 +1,24 @@
<form>
<field>
<id>traceroute.settings.hostname</id>
<label>Hostname or IP</label>
<type>text</type>
<help><![CDATA[Hostname or address to trace.]]></help>
</field>
<field>
<id>traceroute.settings.ipproto</id>
<label>Address Family</label>
<type>dropdown</type>
</field>
<field>
<id>traceroute.settings.protocol</id>
<label>Protocol</label>
<type>dropdown</type>
</field>
<field>
<id>traceroute.settings.source_address</id>
<label>Source address</label>
<type>text</type>
<help><![CDATA[Optional source address.]]></help>
</field>
</form>

View File

@ -197,7 +197,8 @@
<page-diagnostics-traceroute>
<name>Diagnostics: Traceroute</name>
<patterns>
<pattern>diag_traceroute.php*</pattern>
<pattern>ui/diagnostics/traceroute*</pattern>
<pattern>api/diagnostics/traceroute/*</pattern>
</patterns>
</page-diagnostics-traceroute>
<page-firewall-alias-edit>

View File

@ -136,7 +136,7 @@
<PacketCapture VisibleName="Packet Capture" url="/ui/diagnostics/packet_capture"/>
<Ping url="/ui/diagnostics/ping"/>
<TestPort VisibleName="Port Probe" url="/diag_testport.php"/>
<Traceroute VisibleName="Trace Route" url="/diag_traceroute.php"/>
<Traceroute VisibleName="Trace Route" url="/ui/diagnostics/traceroute"/>
</Diagnostics>
</Interfaces>
<Firewall order="40" cssClass="glyphicon glyphicon-fire">

View File

@ -0,0 +1,35 @@
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* 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\Diagnostics;
use OPNsense\Base\BaseModel;
class Traceroute extends BaseModel
{
}

View File

@ -0,0 +1,36 @@
<model>
<mount>:memory:</mount>
<version>1.0.0</version>
<description>
OPNsense Traceroute Diagnostics
</description>
<items>
<settings>
<hostname type="HostnameField">
<Required>Y</Required>
<ValidationMessage>Provide a valid hostname or address to ping</ValidationMessage>
</hostname>
<ipproto type="OptionField">
<Required>Y</Required>
<default>inet</default>
<OptionValues>
<inet>IPv4</inet>
<inet6>IPv6</inet6>
</OptionValues>
</ipproto>
<protocol type="OptionField">
<Required>Y</Required>
<default>udp</default>
<OptionValues>
<udp>UDP</udp>
<icmp>ICMP</icmp>
</OptionValues>
</protocol>
<source_address type="NetworkField">
<Required>N</Required>
<NetMaskAllowed>N</NetMaskAllowed>
<ValidationMessage>Provide a valid source address</ValidationMessage>
</source_address>
</settings>
</items>
</model>

View File

@ -0,0 +1,99 @@
{#
# Copyright (c) 2023 Deciso B.V.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or withoutmodification,
# 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>
$( document ).ready(function() {
var data_get_map = {'frm_TracerouteSettings':"/api/diagnostics/traceroute/get"};
mapDataToFormUI(data_get_map).done(function(data){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
});
$("#btn_query").click(function () {
if (!$("#frm_TracerouteSettings_progress").hasClass("fa-spinner")) {
$("#traceroute_results").hide();
$("#frm_TracerouteSettings_progress").addClass("fa fa-spinner fa-pulse");
let callb = function (data) {
$("#frm_TracerouteSettings_progress").removeClass("fa fa-spinner fa-pulse");
if (data.result === 'ok') {
$("#traceroute_results").show();
if (data.response.notice){
$("#traceroute_notice").html(data.response.notice);
}
$("#traceroute_results > tbody").empty();
data.response.rows.forEach(function(row) {
$tr = $("<tr/>");
$tr.append($("<td/>").append(row.ttl));
$tr.append($("<td/>").append(row.AS));
$tr.append($("<td/>").append(row.host));
$tr.append($("<td/>").append(row.address));
$tr.append($("<td/>").append(row.probes));
$("#traceroute_results > tbody").append($tr);
});
if (data.response.error) {
$tr = $("<tr/>");
$tr.append($("<td colspan='5'>").append(
$('<i class="fa fa-chevron-right" aria-hidden="true"></i>'),
'&nbsp;',
$("<span>").text(data.response.error)
));
$("#traceroute_results > tbody").append($tr);
}
}
}
saveFormToEndpoint("/api/diagnostics/traceroute/set", 'frm_TracerouteSettings', callb, false, callb);
}
});
});
</script>
<div class="tab-content content-box col-xs-12 __mb">
<div id="traceroute">
{{ partial("layout_partials/base_form",['fields':tracerouteForm,'id':'frm_TracerouteSettings', 'apply_btn_id':'btn_query'])}}
</div>
</div>
<div class="tab-content content-box col-xs-12 __mb">
<table class="table table-condensed" id="traceroute_results" style="display:none;">
<thead>
<tr>
<th colspan="4">{{ lang._('Response')}}</th>
</tr>
<tr>
<th colspan="4"><i class="fa fa-chevron-right" aria-hidden="true"></i> <span id="traceroute_notice"></span></th>
</tr>
<tr>
<th>{{ lang._('TTL')}}</th>
<th>{{ lang._('AS#')}}</th>
<th>{{ lang._('Host')}}</th>
<th>{{ lang._('Address')}}</th>
<th>{{ lang._('Probes')}}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>

View File

@ -0,0 +1,88 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2023 Ad Schellevis <ad@opnsense.org>
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.
"""
import argparse
import json
import subprocess
parser = argparse.ArgumentParser()
parser.add_argument('--domain', help='domain name or ip to trace', default='')
parser.add_argument('--ipproto', help='ip protocol version [inet,inet6]', default='inet')
parser.add_argument('--source_address', help='source address to use', default=None)
parser.add_argument('--timeout', help='timeout in seconds', type=int, default=20)
parser.add_argument('--probes', help='number of probes', type=int, default=1)
parser.add_argument('--protocol', help='protocol to use [icmp, udp]', default='udp')
inputargs = parser.parse_args()
result = {'rows': []}
if inputargs.ipproto == 'inet6':
cmd = ['/usr/sbin/traceroute6']
else:
cmd = ['/usr/sbin/traceroute']
cmd = cmd + ['-a', '-w', '2', '-q', '%d' % inputargs.probes]
if inputargs.source_address:
cmd = cmd + ['-s', inputargs.source_address]
if inputargs.protocol.lower() == 'icmp':
cmd.append('-I')
cmd.append(inputargs.domain)
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
try:
outs, errs = proc.communicate(timeout=inputargs.timeout)
except subprocess.TimeoutExpired:
result['error'] = 'timeout reached'
proc.kill()
outs, errs = proc.communicate()
if errs:
result['notice'] = errs.strip()
for line in outs.strip().split('\n'):
parts = line.split()
if len(parts) < 3:
continue
record = {'address': ''}
if parts[0].startswith('['):
parts = [result['rows'][-1]['ttl'] if len(result['rows']) > 0 else ''] + parts
record['ttl'] = parts[0]
record['AS'] = parts[1][1:-1]
record['host'] = parts[2]
if parts[3].startswith('('):
record['address'] = parts[3][1:-1]
record['probes'] = ' '.join(parts[4:])
else:
record['probes'] = ' '.join(parts[3:])
result['rows'].append(record)
print(json.dumps(result))

View File

@ -267,3 +267,9 @@ command:/usr/local/opnsense/scripts/interfaces/ping.py
parameters: --job %s --detail %s view
type:script_output
message:View ping jobid %s (%s)
[traceroute]
command:/usr/local/opnsense/scripts/interfaces/traceroute.py
parameters: --domain %s --ipproto %s --source_address %s --protocol %s
type:script_output
message: traceroute %s

View File

@ -1,186 +0,0 @@
<?php
/*
* Copyright (C) 2018 Franco Fichtner <franco@opnsense.org>
* Copyright (C) 2016 Deciso B.V.
* Copyright (C) 2005 Paul Taylor <paultaylor@winn-dixie.com>
* Copyright (C) 2005 Manuel Kasper <mk@neon1.net>
* 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.
*/
require_once("guiconfig.inc");
require_once("system.inc");
require_once("interfaces.inc");
define('MAX_TTL', 64);
$cmd_output = false;
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// set form defaults
$pconfig = array();
$pconfig['ipproto'] = 'ipv4';
$pconfig['interface'] = null;
$pconfig['useicmp'] = null;
$pconfig['resolve'] = null;
$pconfig['ttl'] = isset($_GET['ttl']) ? $_GET['ttl'] : 18;
$pconfig['host'] = isset($_GET['host']) ? $_GET['host'] : null;
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
// validate input / execute traceroute
$input_errors = array();
$pconfig = $_POST;
/* input validation */
$reqdfields = explode(" ", "host ttl");
$reqdfieldsn = array(gettext("Host"),gettext("ttl"));
do_input_validation($pconfig, $reqdfields, $reqdfieldsn, $input_errors);
if (!is_numeric($pconfig['ttl']) || $pconfig['ttl'] < 1 || $pconfig['ttl'] > MAX_TTL) {
$input_errors[] = sprintf(gettext("Maximum number of hops must be between 1 and %s"), MAX_TTL);
}
$host = trim($pconfig['host']);
$ipproto = $pconfig['ipproto'];
if ($pconfig['ipproto'] == "ipv4" && is_ipaddrv6($host)) {
$input_errors[] = gettext("When using IPv4, the target host must be an IPv4 address or hostname.");
} elseif ($pconfig['ipproto'] == "ipv6" && is_ipaddrv4($host)) {
$input_errors[] = gettext("When using IPv6, the target host must be an IPv6 address or hostname.");
}
if (count($input_errors) == 0) {
$cmd_args = "-w 2";
$cmd_args .= !empty($pconfig['useicmp']) ? " -I " : "";
$cmd_args .= !empty($pconfig['resolve']) ? "" : " -n ";
$cmd_args .= " -m " . escapeshellarg($pconfig['ttl']);
$command = "/usr/sbin/traceroute";
if ($pconfig['ipproto'] == 'ipv6') {
list ($ifaddr) = interfaces_primary_address6($pconfig['interface']);
$command .= '6';
} else {
list ($ifaddr) = interfaces_primary_address($pconfig['interface']);
}
if (is_ipaddr($ifaddr) && (is_ipaddr($host) || is_hostname($host))) {
$cmd_args .= " -s " . escapeshellarg($ifaddr) . " ";
}
$cmd_action = "{$command} {$cmd_args} " . " " . escapeshellarg($host);
$process = proc_open($cmd_action, array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w")), $pipes);
if (is_resource($process)) {
$cmd_output = "# {$cmd_action}\n";
$cmd_output .= stream_get_contents($pipes[2]);
$cmd_output .= stream_get_contents($pipes[1]);
}
}
}
legacy_html_escape_form_data($pconfig);
include("head.inc");
?>
<body>
<?php include("fbegin.inc"); ?>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<section class="col-xs-12">
<?php if (isset($input_errors) && count($input_errors) > 0) print_input_errors($input_errors); ?>
<div class="content-box">
<form method="post" name="iform" id="iform">
<div class="table-responsive">
<table class="table table-striped">
<tbody>
<tr>
<td><?=gettext("Host"); ?></td>
<td><input name="host" type="text" class="form-control" id="host" value="<?=$pconfig['host'];?>" /></td>
</tr>
<tr>
<td><?=gettext("IP Protocol"); ?></td>
<td>
<select name="ipproto" class="selectpicker">
<option value="ipv4" <?=($pconfig['ipproto'] == "ipv4") ? "selected=\"selected\"" : "";?>><?= gettext('IPv4') ?></option>
<option value="ipv6" <?=($pconfig['ipproto'] == "ipv6") ? "selected=\"selected\"" : "";?>><?= gettext('IPv6') ?></option>
<option value="ipv6-ll" <?=$pconfig['ipproto'] == "ipv6-ll" ? "selected=\"selected\"" : "";?>><?= gettext('IPv6 Link-Local') ?></option>
</select>
</td>
</tr>
<tr>
<td><?=gettext("Source Address"); ?></td>
<td>
<select name="interface" class="selectpicker">
<option value=""><?= gettext('Default') ?></option>
<?php foreach (get_configured_interface_with_descr() as $ifname => $ifdescr): ?>
<option value="<?= html_safe($ifname) ?>" <?= $ifname == $pconfig['interface'] ? 'selected="selected"' : '' ?>>
<?= html_safe($ifdescr) ?>
</option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><?=gettext("Maximum number of hops");?></td>
<td>
<select name="ttl" class="selectpicker" id="ttl">
<?php
for ($i = 1; $i <= MAX_TTL; $i++): ?>
<option value="<?=$i;?>" <?= $i == $pconfig['ttl'] ? "selected=\"selected\"" : "" ;?>>
<?=$i;?>
</option>
<?php
endfor; ?>
</select>
</td>
</tr>
<tr>
<td><?=gettext("Reverse Address Lookup");?></td>
<td>
<input name="resolve" type="checkbox"<?=!empty($pconfig['resolve']) ? " checked=\"checked\"" : ""; ?> />
</td>
</tr>
<tr>
<td><?=gettext("Use ICMP");?></td>
<td>
<input name="useicmp" type="checkbox" <?=!empty($pconfig['useicmp']) ? " checked=\"checked\"" : ""; ?> />
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<button name="Submit" type="submit" class="btn btn-primary" value="yes"><?= html_safe(gettext('Traceroute')) ?></button>
</td>
</tr>
</tbody>
</table>
</div>
</form>
</div>
</section>
<?php if (!empty($cmd_output)): ?>
<section class="col-xs-12">
<pre><?=htmlspecialchars($cmd_output);?></pre>
</section>
<?php endif ?>
</div>
</div>
</section>
<?php include("foot.inc"); ?>