packet capture, support multiple interfaces. closes https://github.com/opnsense/core/issues/2871

It's a short term solution, it would be better to refactor the legacy page and use configd calls, but since the "any" keyword wasn't possible in fbsd, it seemed like a good idea to allow multiple selections.

Changes in this commit:

- start tcpdump captures to files using /tmp/packetcapture_[INTERFACE].cap in stead of the single file before
- refactor "remove" action to delete all /tmp/packetcapture_*.cap
- refactor "view" action to iterate over all /tmp/packetcapture_*.cap files and return a named array per interface
- change download action to point to the actual filename and return content of found.
This commit is contained in:
Ad Schellevis 2019-07-07 15:22:00 +02:00
parent 7f84bff41e
commit 922ab1a95b
1 changed files with 115 additions and 63 deletions

View File

@ -34,7 +34,7 @@ require_once("interfaces.inc");
*/
function stop_capture()
{
$processes_running = trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep tcpdump | /usr/bin/grep packetcapture.cap | /usr/bin/egrep -v '(pflog|grep)'"));
$processes_running = trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep tcpdump | /usr/bin/grep packetcapture_ | /usr/bin/egrep -v '(pflog|grep)'"));
foreach (explode("\n", $processes_running) as $process) {
exec("kill ". explode(' ',$process)[0]);
}
@ -48,8 +48,6 @@ function start_capture($options)
{
$cmd_opts = array();
$filter_opts = array();
$intf = get_real_interface($options['interface']);
$cmd_opts[] = '-i ' . $intf;
if (empty($options['promiscuous'])) {
// disable promiscuous mode
@ -104,14 +102,20 @@ function start_capture($options)
$filter_opts[] = "port " . str_replace("!", "not ", $options['port']);
}
if (!empty($intf)) {
$cmd = '/usr/sbin/tcpdump ';
$cmd .= implode(' ', $cmd_opts);
$cmd .= ' -w /tmp/packetcapture.cap ';
$cmd .= " ".escapeshellarg(implode(' and ', $filter_opts));
//delete previous packet capture if it exists
@unlink('/tmp/packetcapture.cap');
mwexec_bg($cmd);
foreach (glob("/tmp/packetcapture_*.cap") as $filename) {
@unlink($filename);
}
foreach ($options['interface'] as $key) {
$intf = get_real_interface($key);
if (!empty($intf)) {
$cmd = '/usr/sbin/tcpdump ';
$cmd .= "-i " . escapeshellarg($intf) . " ";
$cmd .= implode(' ', $cmd_opts);
$cmd .= " -w /tmp/packetcapture_{$intf}.cap ";
$cmd .= " ".escapeshellarg(implode(' and ', $filter_opts));
//delete previous packet capture if it exists
mwexec_bg($cmd);
}
}
}
@ -121,7 +125,7 @@ function start_capture($options)
*/
function capture_running()
{
$processcheck = (trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep tcpdump | /usr/bin/grep packetcapture.cap | /usr/bin/egrep -v '(pflog|grep)'")));
$processcheck = (trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep tcpdump | /usr/bin/grep packetcapture_ | /usr/bin/egrep -v '(pflog|grep)'")));
if (!empty($processcheck)) {
return true;
} else {
@ -150,50 +154,62 @@ foreach (array('server', 'client') as $mode) {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (isset($_GET['download'])) {
// download capture file
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=packetcapture.cap");
header("Content-Length: ".filesize("/tmp/packetcapture.cap"));
$file = fopen("/tmp/packetcapture.cap", "r");
while(!feof($file)) {
print(fread($file, 32 * 1024));
ob_flush();
foreach (glob("/tmp/packetcapture_*.cap") as $filename) {
$bfilename = basename($filename);
if ($_GET['download'] === $bfilename) {
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$bfilename}");
header("Content-Length: ".filesize($filename));
$file = fopen($filename, "r");
while(!feof($file)) {
print(fread($file, 32 * 1024));
ob_flush();
}
fclose($file);
break;
}
}
fclose($file);
exit;
} elseif (!empty($_GET['view'])) {
// download capture contents
if (!empty($_GET['dnsquery'])) {
//if dns lookup is checked
$disabledns = "";
} else {
//if dns lookup is unchecked
$disabledns = "-n";
}
$detail_args = "";
switch (!empty($_GET['detail']) ? $_GET['detail'] : null) {
case "full":
$detail_args = "-vv -e";
break;
case "high":
$detail_args = "-vv";
break;
case "medium":
$detail_args = "-v";
break;
case "normal":
default:
$detail_args = "-q";
break;
}
$result = array();
$dump_output = array();
exec("/usr/sbin/tcpdump {$disabledns} {$detail_args} -r /tmp/packetcapture.cap | /usr/bin/tail -n 5000", $dump_output);
// reformat raw output to 1 packet per array item
foreach ($dump_output as $line) {
if ($line[0] == ' ' && count($result) > 0) {
$result[count($result)-1] .= "\n" . $line;
$result = [];
foreach (glob("/tmp/packetcapture_*.cap") as $filename) {
$intf = explode(".", substr(basename($filename), 14))[0];
$intf_key = convert_real_interface_to_friendly_interface_name($intf);
$intf_name = !empty($interfaces[$intf_key]) ? $interfaces[$intf_key] : $intf_key;
$result[$intf] = ['name' => $intf_name, 'content' => []];
// download capture contents
if (!empty($_GET['dnsquery'])) {
//if dns lookup is checked
$disabledns = "";
} else {
$result[] = $line;
//if dns lookup is unchecked
$disabledns = "-n";
}
$detail_args = "";
switch (!empty($_GET['detail']) ? $_GET['detail'] : null) {
case "full":
$detail_args = "-vv -e";
break;
case "high":
$detail_args = "-vv";
break;
case "medium":
$detail_args = "-v";
break;
case "normal":
default:
$detail_args = "-q";
break;
}
$dump_output = array();
exec("/usr/sbin/tcpdump {$disabledns} {$detail_args} -r {$filename} | /usr/bin/tail -n 5000", $dump_output);
// reformat raw output to 1 packet per array item
foreach ($dump_output as $line) {
if ($line[0] == ' ' && count($result) > 0) {
$result[$intf]['content'][count($result)-1] .= "\n" . $line;
} else {
$result[$intf]['content'][] = $line;
}
}
}
echo json_encode($result);
@ -201,7 +217,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
} else {
// set form defaults
$pconfig = array();
$pconfig['interface'] = "wan";
$pconfig['interface'] = ["wan"];
$pconfig['promiscuous'] = null;
$pconfig['fam'] = null;
$pconfig['proto'] = null;
@ -215,8 +231,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$pconfig = $_POST;
if (!empty($_POST['start'])) {
if (!array_key_exists($pconfig['interface'], $interfaces)) {
$input_errors[] = gettext("Invalid interface.");
foreach ($pconfig['interface'] as $key) {
if (!array_key_exists($key, $interfaces)) {
$input_errors[] = sprintf(gettext("Invalid interface %s."), $key);
}
}
if ($pconfig['fam'] !== "" && $pconfig['fam'] !== "ip" && $pconfig['fam'] !== "ip6") {
$input_errors[] = gettext("Invalid address family.");
@ -249,7 +267,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
} elseif (!empty($pconfig['stop'])) {
stop_capture();
} elseif (!empty($pconfig['remove'])) {
@unlink('/tmp/packetcapture.cap');
foreach (glob("/tmp/packetcapture_*.cap") as $filename) {
@unlink($filename);
}
header(url_safe('Location: /diag_packet_capture.php'));
exit;
}
@ -269,10 +289,22 @@ include("head.inc");
data: {view: 'view', 'dnsquery': $("#dnsquery:checked").val() ,'detail': $("#detail").val()},
success: function(response) {
var html = [];
$.each(response, function(idx, line){
html.push('<tr><td>'+line+'</td></tr>');
$.each(response, function(intf, data){
$.each(data['content'], function(idx, line){
html.push(
$("<tr>").append(
$("<td>").append(
$("<span>").text(data['name']),
$("<br>"),
$("<small>").text(intf),
)
).append(
$("<td>").text(line)
)
);
});
});
$("#capture_output").html(html.join(''));
$("#capture_output").empty().append(html);
$("#capture").removeClass('hidden');
// scroll to capture output
$('html, body').animate({
@ -311,10 +343,10 @@ include("fbegin.inc");
<tr>
<td><a id="help_for_if" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Interface");?></td>
<td>
<select name="interface" class="selectpicker">
<select name="interface[]" class="selectpicker" multiple="multiple">
<?php
foreach ($interfaces as $iface => $ifacename): ?>
<option value="<?=$iface;?>" <?=$pconfig['interface'] == $iface ? "selected=\"selected\"" : ""; ?>>
<option value="<?=$iface;?>" <?=in_array($iface, $pconfig['interface']) ? "selected=\"selected\"" : ""; ?>>
<?=$ifacename;?>
</option>
<?php
@ -456,10 +488,29 @@ include("fbegin.inc");
else:?>
<input type="submit" class="btn" name="start" value="<?= html_safe(gettext('Start')) ?>"/>
<?php
if (file_exists('/tmp/packetcapture.cap')):?>
if (count(glob('/tmp/packetcapture_*.cap')) > 0):?>
<button type="button" id="view" class="btn"> <?=gettext("View Capture");?> </button>
<a href="?download" type="submit" class="btn"><?=gettext("Download Capture");?></a>
<input type="submit" class="btn" name="remove" value="<?= html_safe(gettext('Delete Capture')) ?>"/>
<table class="table table-condensed">
<thead>
<tr>
<th><?=gettext("Download Capture");?></th>
</tr>
</thead>
<tbody>
<?php
foreach (glob("/tmp/packetcapture_*.cap") as $filename):?>
<tr>
<td><a href="?download=<?=basename($filename);?>">
<i class="fa fa-file"></i>
<?=basename($filename);?>
</a>
</td>
</tr>
<?php
endforeach;?>
</tbody>
</table>
<?php
endif;
endif;?>
@ -477,6 +528,7 @@ include("fbegin.inc");
<table class="table table-condensed">
<thead>
<tr>
<th><?=gettext("Interface");?></th>
<th><?=gettext("Capture output");?></th>
</tr>
</thead>