mirror of
https://github.com/Ysurac/openmptcprouter-feeds.git
synced 2025-02-12 10:31:51 +00:00
Add luci-app-packet-capture
This commit is contained in:
parent
19dde3a38a
commit
93694b5983
9 changed files with 489 additions and 2 deletions
14
luci-app-packet-capture/Makefile
Normal file
14
luci-app-packet-capture/Makefile
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright 2020 Wojciech Jowsa (wojciech.jowsa@gmail.com)
|
||||
# This is free software, licensed under the Apache License, Version 2.0
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Packet capture application
|
||||
LUCI_DEPENDS:=+luci-mod-admin-full +tcpdump +uhttpd-mod-ubus +coreutils +coreutils-timeout
|
||||
|
||||
PKG_MAINTAINER:=Wojciech Jowsa <wojciech.jowsa@gmail.com>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signatureet
|
|
@ -0,0 +1,287 @@
|
|||
'use strict';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require fs';
|
||||
'require form';
|
||||
'require network';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
var eventSource,
|
||||
captureFilePoll,
|
||||
hostName;
|
||||
|
||||
function stopTcpdump() {
|
||||
fs.exec("/usr/libexec/packet_capture_stop").then(function(replay) {
|
||||
if (eventSource)
|
||||
eventSource.close();
|
||||
}.bind(this)).catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', stopTcpdump);
|
||||
|
||||
var callLuciProcessList = rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'getProcessList',
|
||||
expect: { result: [] }
|
||||
});
|
||||
|
||||
var callInitAction = rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'setInitAction',
|
||||
params: [ 'name', 'action' ],
|
||||
expect: { result: false }
|
||||
});
|
||||
|
||||
function addOutput() {
|
||||
var tcpdumpOut = document.querySelectorAll('[id$="tcpdump_out"]')[0];
|
||||
if (tcpdumpOut)
|
||||
return;
|
||||
|
||||
var frameEl = E('div', {'class': 'cbi-value'});
|
||||
|
||||
frameEl.appendChild(E('textarea', {
|
||||
'id': 'tcpdump_out',
|
||||
'class': 'cbi-input-textarea',
|
||||
'readonly': '',
|
||||
'style': 'width:100%',
|
||||
'rows': 30,
|
||||
}));
|
||||
|
||||
frameEl.firstElementChild.style.fontFamily = 'monospace';
|
||||
|
||||
var downloadBtn = document.querySelectorAll('[id$="download_file"]')[0];
|
||||
if (downloadBtn)
|
||||
downloadBtn.parentNode.insertBefore(frameEl, downloadBtn.nextSibling);
|
||||
}
|
||||
|
||||
var downloadCaptureFile = function(ev) {
|
||||
var form = E('form', {
|
||||
method: 'post',
|
||||
action: '/cgi-bin/cgi-download',
|
||||
enctype: 'application/x-www-form-urlencoded'
|
||||
}, E('input', { type: 'hidden', name: 'sessionid', value: rpc.getSessionID()},
|
||||
E('input', { type: 'hidden', name: 'path', value: "/tmp/capture.pcap"},
|
||||
E('input', { type: 'hidden', name: 'filename', value: hostName + "-" + Date.now() + ".pcap"},
|
||||
E('input', { type: 'hidden', name: 'mimetype', value: 'application/vnd.tcpdump.pcap'}
|
||||
)))));
|
||||
|
||||
ev.currentTarget.parentNode.appendChild(form);
|
||||
form.submit();
|
||||
form.parentNode.removeChild(form);
|
||||
}
|
||||
|
||||
function subscribeTcpdump() {
|
||||
if (eventSource)
|
||||
eventSource.close();
|
||||
|
||||
eventSource = new EventSource('/ubus/subscribe/tcpdump' + '?' + rpc.getSessionID());
|
||||
eventSource.onerror = function(event) {
|
||||
eventSource.close();
|
||||
console.log(event);
|
||||
};
|
||||
|
||||
addOutput();
|
||||
var textOut = document.querySelectorAll('[id$="tcpdump_out"]')[0];
|
||||
textOut.value = "";
|
||||
eventSource.addEventListener("tcpdump.data", function(event) {
|
||||
textOut.value = textOut.value + "\n" + JSON.parse(event.data).data;
|
||||
});
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
var tasks = [];
|
||||
tasks.push(fs.stat("/var/run/packet_capture.pid").then(L.bind(function(res) {
|
||||
var downloadBtn = document.querySelectorAll('[id$="download_file"]')[0];
|
||||
if (!downloadBtn)
|
||||
return;
|
||||
if (!eventSource || eventSource.readyState == 2)
|
||||
subscribeTcpdump();
|
||||
var textOut = document.querySelectorAll('[id$="tcpdump_out"]')[0];
|
||||
if (textOut)
|
||||
textOut.style.borderColor = "green";
|
||||
var startBtn = document.querySelectorAll('[id$="start_tcpdump"]')[0];
|
||||
if (startBtn)
|
||||
startBtn.hidden = true;
|
||||
var stopBtn = document.querySelectorAll('[id$="stop_tcpdump"]')[0];
|
||||
if (stopBtn)
|
||||
stopBtn.hidden = false;
|
||||
return;
|
||||
})).catch(function(error) {
|
||||
var textOut = document.querySelectorAll('[id$="tcpdump_out"]')[0];
|
||||
if (textOut)
|
||||
textOut.style.borderColor = "red";
|
||||
var startBtn = document.querySelectorAll('[id$="start_tcpdump"]')[0];
|
||||
if (startBtn)
|
||||
startBtn.hidden = false;
|
||||
var stopBtn = document.querySelectorAll('[id$="stop_tcpdump"]')[0];
|
||||
if (stopBtn)
|
||||
stopBtn.hidden = true;
|
||||
if (eventSource)
|
||||
eventSource.close();
|
||||
}));
|
||||
|
||||
return Promise.all(tasks);
|
||||
}
|
||||
|
||||
function updatePollCheckCaptureFileExists() {
|
||||
checkCaptureFileExists();
|
||||
L.Poll.remove(captureFilePoll);
|
||||
L.Poll.add(L.bind(checkCaptureFileExists, m),5);
|
||||
}
|
||||
|
||||
function checkCaptureFileExists() {
|
||||
var tasks = [];
|
||||
tasks.push(fs.stat("/tmp/capture.pcap").then(L.bind(function(res) {
|
||||
var downloadBtn = document.querySelector('[data-action="download"]');
|
||||
if (!downloadBtn)
|
||||
return;
|
||||
var downloadCheckBox = document.querySelectorAll('[data-widget-id$="file"]')[0].checked;
|
||||
if (!downloadCheckBox) {
|
||||
fs.remove("/tmp/capture.pcap").then(function(replay) {
|
||||
downloadBtn.disabled = true;;
|
||||
}.bind(this)).catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
} else {
|
||||
downloadBtn.disabled = false;
|
||||
}
|
||||
})).catch(function(error) {
|
||||
var downloadBtn = document.querySelector('[data-action="download"]');
|
||||
if (downloadBtn)
|
||||
downloadBtn.disabled = true;
|
||||
}));
|
||||
|
||||
return Promise.all(tasks);
|
||||
}
|
||||
|
||||
return L.view.extend({
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('system')
|
||||
]);
|
||||
},
|
||||
|
||||
handleDownload: function(ev) {
|
||||
downloadCaptureFile(ev);
|
||||
},
|
||||
|
||||
render: function(processes) {
|
||||
var m, s, o;
|
||||
|
||||
hostName = uci.get('system', '@system[0]', 'hostname');
|
||||
|
||||
m = new form.Map('packet_capture', _('Packet Capture - Tcpdump'), _('Capture packets with tcpdump.'));
|
||||
s = m.section(form.TypedSection, 'tcpdump');
|
||||
s.anonymous = 1;
|
||||
|
||||
o = s.option(widgets.DeviceSelect, 'interface', _('Interface'), _(''));
|
||||
o.noaliases = true;
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.filter = function(section_id, value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
o = s.option(form.Value, 'filter', _('Filter'), _('Tcpdump filter like protocol, port etc.'));
|
||||
o.modalonly = false;
|
||||
o.datatype = 'and(minlength(1),maxlength(1024))';
|
||||
|
||||
o = s.option(form.Value, 'duration', _('Duration'), _('Duration of packet capturing in seconds.'));
|
||||
o.modalonly = false;
|
||||
o.datatype = 'range(1,4294967296)';
|
||||
|
||||
o = s.option(form.Value, 'packets', _('Packets'), _('Number of packets to be captured.'));
|
||||
o.modalonly = false;
|
||||
o.datatype = 'range(1,4294967296)';
|
||||
|
||||
o = s.option(form.Flag, 'domains', _('Resolve domains'), _("Convert host addresses to names."));
|
||||
|
||||
o = s.option(form.Flag, 'verbose', _('Verbose output'), _("Print the link-level header on each dump line."));
|
||||
|
||||
o = s.option(form.Flag, 'file', _('Save to file'), _("Save capture to pcap file."));
|
||||
|
||||
o = s.option(form.Button, 'start_tcpdump', _('Start tcpdump'), _(''));
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = ui.createHandlerFn(this, function(section_id, ev) {
|
||||
var downloadBtn = document.querySelector('[data-action="download"]');
|
||||
if (!downloadBtn)
|
||||
return;
|
||||
fs.remove("/tmp/capture.pcap").then(function(replay) {
|
||||
downloadBtn.disabled = true;;
|
||||
}.bind(this)).catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
var iface = document.querySelectorAll('[id$="interface"]')[1].value,
|
||||
filter = document.querySelectorAll('[id$="filter"]')[2].value,
|
||||
packets = document.querySelectorAll('[id$="packets"]')[2].value,
|
||||
duration = document.querySelectorAll('[id$="duration"]')[2].value,
|
||||
verbose = document.querySelectorAll('[data-widget-id$="verbose"]')[0].checked,
|
||||
domains = document.querySelectorAll('[data-widget-id$="domains"]')[0].checked,
|
||||
file = document.querySelectorAll('[data-widget-id$="file"]')[0].checked
|
||||
|
||||
var args = {
|
||||
"interface": iface,
|
||||
"filter": filter,
|
||||
"packets": packets,
|
||||
"duration": duration,
|
||||
"verbose": verbose,
|
||||
"domains": domains,
|
||||
"file": file
|
||||
}
|
||||
|
||||
return fs.exec_direct('/usr/libexec/packet_capture_start', [JSON.stringify(args)]).then(function(replay) {
|
||||
var error_position = replay.search("error:");
|
||||
if (error_position != -1){
|
||||
ui.showModal(_(replay.substring(error_position + 6, replay.length)), [
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-negative important',
|
||||
'click': function(ev) {
|
||||
ui.hideModal();
|
||||
}
|
||||
}, _('Close')),
|
||||
])
|
||||
]);
|
||||
return;
|
||||
}
|
||||
rpc.list.apply(rpc).then(function(res) {
|
||||
for (var k in res) {
|
||||
if (res[k] == "tcpdump" )
|
||||
subscribeTcpdump()
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this)).catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
|
||||
o = s.option(form.Button, 'stop_tcpdump', _('Stop tcpdump'), _(''));
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = ui.createHandlerFn(this, function(section_id, ev) {
|
||||
if (!eventSource)
|
||||
return;
|
||||
return fs.exec("/usr/libexec/packet_capture_stop").then(function(replay) {
|
||||
eventSource.close();
|
||||
}.bind(this)).catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
|
||||
o = s.option(form.Button, 'download_file', _('Download capture file'));
|
||||
o.inputstyle = 'action important';
|
||||
o.inputtitle = _('Download');
|
||||
o.data_action = 'download'
|
||||
o.onclick = this.handleDownload;
|
||||
|
||||
L.Poll.add(L.bind(updateButtons, m),1);
|
||||
captureFilePoll = L.bind(updatePollCheckCaptureFileExists, m);
|
||||
L.Poll.add(captureFilePoll,1);
|
||||
|
||||
return m.render();
|
||||
},
|
||||
});
|
1
luci-app-packet-capture/root/etc/config/packet_capture
Normal file
1
luci-app-packet-capture/root/etc/config/packet_capture
Normal file
|
@ -0,0 +1 @@
|
|||
config tcpdump
|
64
luci-app-packet-capture/root/usr/libexec/packet_capture
Executable file
64
luci-app-packet-capture/root/usr/libexec/packet_capture
Executable file
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env lua
|
||||
|
||||
local ubus = require "ubus"
|
||||
local fs = require "nixio.fs"
|
||||
|
||||
local conn = ubus.connect()
|
||||
if not conn then
|
||||
error("Failed to connect to ubus")
|
||||
return
|
||||
end
|
||||
|
||||
local args = "-n"
|
||||
local duration = ""
|
||||
|
||||
if arg[1] ~= nil then
|
||||
args = arg[1]
|
||||
if arg[2] ~= "" then
|
||||
duration = arg[2]
|
||||
end
|
||||
end
|
||||
|
||||
local filter = fs.stat("/tmp/tcpdump_filter")
|
||||
if filter then
|
||||
args = args .. " -F /tmp/tcpdump_filter"
|
||||
end
|
||||
|
||||
local ubus_objects = {
|
||||
tcpdump = {
|
||||
}
|
||||
}
|
||||
|
||||
conn:add( ubus_objects )
|
||||
|
||||
os.execute("sleep 1")
|
||||
|
||||
local command = "tcpdump -l " .. args .. " 2>&1"
|
||||
|
||||
if duration ~= "" then
|
||||
command = "timeout " .. duration .. " " .. command
|
||||
end
|
||||
|
||||
local pipe = io.popen(command)
|
||||
|
||||
for line in pipe:lines() do
|
||||
local params = {
|
||||
data = line
|
||||
}
|
||||
conn:notify(ubus_objects.tcpdump.__ubusobj, "tcpdump.data", params)
|
||||
end
|
||||
|
||||
local pcap = fs.stat("/tmp/capture.pcap0")
|
||||
if pcap then
|
||||
fs.move("/tmp/capture.pcap0","/tmp/capture.pcap")
|
||||
fs.remove("/tmp/capture.pcap1")
|
||||
end
|
||||
|
||||
if filter then
|
||||
fs.remove("/tmp/tcpdump_filter")
|
||||
end
|
||||
|
||||
conn:close()
|
||||
pipe:close()
|
||||
|
||||
fs.remove("/var/run/packet_capture.pid")
|
69
luci-app-packet-capture/root/usr/libexec/packet_capture_start
Executable file
69
luci-app-packet-capture/root/usr/libexec/packet_capture_start
Executable file
|
@ -0,0 +1,69 @@
|
|||
#!/bin/sh
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
PIDFILE="/var/run/packet_capture.pid"
|
||||
|
||||
if [ -f "$PIDFILE"];then
|
||||
echo "error: Packet capture is running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
json_load "$1"
|
||||
json_get_var interface interface
|
||||
json_get_var filter filter
|
||||
json_get_var duration duration
|
||||
json_get_var packets packets
|
||||
json_get_var verbose verbose
|
||||
json_get_var domains domains
|
||||
json_get_var file file
|
||||
|
||||
args="-n"
|
||||
|
||||
if [ "$domains" == "1" ];then
|
||||
args=""
|
||||
fi
|
||||
|
||||
if [ -n "$interface" ];then
|
||||
ip a show "$interface" > /dev/null 2>&1
|
||||
if [ "$?" == "1" ]; then
|
||||
echo "error: Incorrect format of an interface"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
args="$args -i $interface"
|
||||
fi
|
||||
|
||||
if [ -n "$packets" ];then
|
||||
echo "$packets" | egrep '^[0-9]*$'
|
||||
if [ "$?" -eq 0 ];then
|
||||
args="$args -c $packets"
|
||||
else
|
||||
echo "error: Incorrect packets argument"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$verbose" == "1" ];then
|
||||
args="$args -e"
|
||||
fi
|
||||
|
||||
if [ "$file" == "1" ];then
|
||||
mem=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
|
||||
args="$args -W 2 -C $((mem/(1024 * 10))) -w /tmp/capture.pcap -z /usr/libexec/packet_capture_stop"
|
||||
fi
|
||||
|
||||
if [ -n "$filter" ];then
|
||||
tcpdump -i lo -d "$filter" >/dev/null 2>/dev/null
|
||||
if [ $? -eq 1 ];then
|
||||
echo "error: Incorrect filter argument"
|
||||
exit 1
|
||||
fi
|
||||
echo "$filter" > /tmp/tcpdump_filter
|
||||
fi
|
||||
|
||||
(/usr/libexec/packet_capture "$args" "$duration")&
|
||||
|
||||
echo $! > /var/run/packet_capture.pid
|
||||
|
||||
exit 0
|
9
luci-app-packet-capture/root/usr/libexec/packet_capture_stop
Executable file
9
luci-app-packet-capture/root/usr/libexec/packet_capture_stop
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
pid=$(cat /var/run/packet_capture.pid)
|
||||
if [ -n "$pid" ] && grep -sq packet_capture "/proc/$pid/cmdline"; then
|
||||
ppid=$(pgrep -P $pid)
|
||||
kill -TERM $ppid
|
||||
fi
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"admin/services/packet_capture": {
|
||||
"title": "Packet Capture",
|
||||
"order": 90,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "packet_capture/tcpdump"
|
||||
},
|
||||
"depends" : {
|
||||
"acl": [ "luci-app-packet-capture" ],
|
||||
"uci": { "packet_capture": true },
|
||||
"fs": { "/usr/libexec/packet_capture": "executable",
|
||||
"/usr/libexec/packet_capture_start": "executable",
|
||||
"/usr/libexec/packet_capture_stop": "executable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"luci-app-packet-capture": {
|
||||
"description": "Grant access to tcpdump ubus object",
|
||||
"read": {
|
||||
"cgi-io": [ "download", "exec" ],
|
||||
"ubus": {
|
||||
"tcpdump": [ "*" ],
|
||||
"luci": [ "getProcessList" ]
|
||||
},
|
||||
"uci": [ "packet_capture", "system" ],
|
||||
"file": {
|
||||
"/tmp/capture.pcap": [ "read" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "packet_capture" ],
|
||||
"file": {
|
||||
"/usr/libexec/packet_capture_start": [ "exec" ],
|
||||
"/usr/libexec/packet_capture_stop": [ "exec" ],
|
||||
"/usr/libexec/packet_capture": [ "exec" ],
|
||||
"/tmp/capture.pcap": [ "write" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,9 +78,9 @@ MY_DEPENDS := \
|
|||
!TARGET_mvebu:kmod-usb-net-huawei-cdc-ncm !TARGET_mvebu:kmod-usb-net-rndis !TARGET_mvebu:kmod-usb-net-cdc-ether !TARGET_mvebu:kmod-usb-net-ipheth !TARGET_mvebu:usbmuxd \
|
||||
kmod-rt2800-usb kmod-rtl8xxxu kmod-rtl8192cu kmod-net-rtl8192su \
|
||||
!TARGET_mvebu:luci-proto-qmi wpad-basic kmod-mt7601u kmod-rtl8187 \
|
||||
luci-app-mlvpn mlvpn 464xlat !TARGET_mvebu:kmod-usb-net-smsc75xx kmod-zram kmod-swconfig swconfig kmod-ipt-nat kmod-ipt-nat6 luci-app-https-dns-proxy kmod-tcp-nanqinlang kmod-tcp-bbr2 iptables-mod-ipopt igmpproxy ss iptraf-ng \
|
||||
luci-app-mlvpn mlvpn 464xlat !TARGET_mvebu:kmod-usb-net-smsc75xx kmod-zram kmod-swconfig swconfig kmod-ipt-nat kmod-ipt-nat6 luci-app-https-dns-proxy kmod-tcp-nanqinlang (TARGET_x86_64||TARGET_arm64):kmod-tcp-bbr2 iptables-mod-ipopt igmpproxy ss iptraf-ng \
|
||||
luci-app-acl block-mount blockd fstools luci-app-shutdown libwebp luci-proto-gre tcptraceroute luci-proto-mbim kmod-rtl8xxxu kmod-ath9k-htc luci-app-ttyd luci-mod-dashboard (TARGET_x86||TARGET_x86_64):rtl8192eu-firmware kmod-usb2 libustream-wolfssl (TARGET_x86||TARGET_x86_64):kmod-ixgbevf \
|
||||
hwinfo (TARGET_x86||TARGET_x86_64):dmidecode
|
||||
hwinfo (TARGET_x86||TARGET_x86_64):dmidecode luci-app-packet-capture
|
||||
# luci-theme-bootstrap luci-theme-openwrt-2020 luci-theme-openwrt luci-app-status
|
||||
# luci-proto-bonding luci-app-statistics luci-proto-gre
|
||||
# softethervpn5-client softethervpn5-server luci-app-nginx-ha
|
||||
|
|
Loading…
Reference in a new issue