mirror of
https://github.com/webmin/webmin.git
synced 2026-03-20 16:50:24 +00:00
Merge pull request #2371 from webmin/dev/bandwidth-firewalld
Add support for bandwidth monitoring with Firewalld and Journald
This commit is contained in:
@@ -8,7 +8,11 @@
|
||||
BEGIN { push(@INC, ".."); };
|
||||
use WebminCore;
|
||||
&init_config();
|
||||
if (&foreign_installed("syslog-ng", 1) == 2) {
|
||||
if (&has_command('journalctl')) {
|
||||
$syslog_module = undef;
|
||||
$syslog_journald = "journald" ;
|
||||
}
|
||||
elsif (&foreign_installed("syslog-ng", 1) == 2) {
|
||||
&foreign_require("syslog-ng");
|
||||
$syslog_module = "syslog-ng";
|
||||
}
|
||||
@@ -16,18 +20,20 @@ elsif (&foreign_installed("syslog")) {
|
||||
&foreign_require("syslog");
|
||||
$syslog_module = "syslog";
|
||||
}
|
||||
else {
|
||||
$syslog_module = undef;
|
||||
}
|
||||
&foreign_require("cron", "cron-lib.pl");
|
||||
&foreign_require("net", "net-lib.pl");
|
||||
|
||||
%access = &get_module_acl();
|
||||
|
||||
$bandwidth_log = $config{'bandwidth_log'} || "/var/log/bandwidth";
|
||||
$hours_dir = $config{'bandwidth_dir'} || "$module_config_directory/hours";
|
||||
$hours_dir = $config{'bandwidth_dir'} ||
|
||||
(-e "$module_config_directory/hours" ?
|
||||
"$module_config_directory/hours" :
|
||||
"$module_var_directory/hours");
|
||||
$cron_cmd = "$module_config_directory/rotate.pl";
|
||||
$pid_file = "$module_config_directory/rotate.pid";
|
||||
$pid_file = -e "$module_config_directory/rotate.pid" ?
|
||||
"$module_config_directory/rotate.pid" :
|
||||
"$module_var_directory/rotate.pid";
|
||||
|
||||
# list_hours()
|
||||
# Returns a list of all hours for which traffic is available
|
||||
@@ -163,7 +169,7 @@ return &ui_textbox("$_[2]_hour", $_[0], 2).":".
|
||||
sub detect_firewall_system
|
||||
{
|
||||
local $m;
|
||||
foreach $m ("shorewall", "firewall", "ipfw", "ipfilter") {
|
||||
foreach $m ("shorewall", "firewalld", "firewall", "ipfw", "ipfilter") {
|
||||
return $m if (&check_firewall_system($m));
|
||||
}
|
||||
return undef;
|
||||
@@ -220,6 +226,144 @@ sub is_server_port
|
||||
{
|
||||
}
|
||||
|
||||
############### functions for FirewallD #################
|
||||
|
||||
# get_firewalld_rule(family, chain, direction, iface)
|
||||
# Returns the rich rule for logging packets
|
||||
sub get_firewalld_rule
|
||||
{
|
||||
my ($family, $chain, $direction, $iface) = @_;
|
||||
|
||||
# Define rule components
|
||||
my %switch = (
|
||||
'in' => 'i',
|
||||
'out' => 'o',
|
||||
);
|
||||
|
||||
# Construct and return the rich rule
|
||||
my $udirection = uc($direction);
|
||||
my $rule = {
|
||||
'family' => $family,
|
||||
'table' => 'filter',
|
||||
'chain' => uc($chain),
|
||||
'priority' => 0,
|
||||
'rule' => "-$switch{$direction} $iface -j LOG \
|
||||
--log-prefix BANDWIDTH_$udirection:",
|
||||
};
|
||||
return &firewalld::construct_direct_rule($rule);
|
||||
}
|
||||
|
||||
# check_firewalld_rules(iface)
|
||||
# Returns 1 if the FirewallD rules needed are setup, 0 if not
|
||||
sub check_firewalld_rules
|
||||
{
|
||||
my $iface = shift // $config{'iface'};
|
||||
&foreign_require("firewalld"); # Load the firewalld module
|
||||
my %ip_families = &firewalld::check_ip_family();
|
||||
my @directions = ('in', 'out');
|
||||
my @chains = ('input', 'output', 'forward');
|
||||
my $conflicting = sub {
|
||||
my ($chain, $direction) = @_;
|
||||
return 1 if ($direction eq 'out' && $chain eq 'input');
|
||||
return 1 if ($direction eq 'in' && $chain eq 'output');
|
||||
};
|
||||
|
||||
# Check each direction for each available IP family
|
||||
for my $chain (@chains) {
|
||||
# If IPv4 is available
|
||||
if ($ip_families{ipv4}) {
|
||||
foreach my $direction (@directions) {
|
||||
next if ($conflicting->($chain, $direction));
|
||||
return 0 if (!&firewalld::check_direct_rule(
|
||||
&get_firewalld_rule('ipv4', $chain,
|
||||
$direction, $iface)));
|
||||
}
|
||||
}
|
||||
# If IPv6 is available
|
||||
if ($ip_families{ipv6}) {
|
||||
foreach my $direction (@directions) {
|
||||
next if ($conflicting->($chain, $direction));
|
||||
return 0 if (!&firewalld::check_direct_rule(
|
||||
&get_firewalld_rule('ipv6', $chain,
|
||||
$direction, $iface)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# setup_firewalld_rules()
|
||||
# If any FirewallD rules are missing, add them
|
||||
sub firewalld_rules_control
|
||||
{
|
||||
my ($action, $iface) = @_;
|
||||
&foreign_require("firewalld");
|
||||
my %ip_families = &firewalld::check_ip_family();
|
||||
my @directions = ('in', 'out');
|
||||
my @chains = ('input', 'output', 'forward');
|
||||
my $conflicting = sub {
|
||||
my ($chain, $direction) = @_;
|
||||
return 1 if ($direction eq 'out' && $chain eq 'input');
|
||||
return 1 if ($direction eq 'in' && $chain eq 'output');
|
||||
};
|
||||
|
||||
# Add the rules for each direction and IP family
|
||||
foreach my $chain (@chains) {
|
||||
if ($ip_families{ipv4}) {
|
||||
foreach my $direction (@directions) {
|
||||
next if ($conflicting->($chain, $direction));
|
||||
my ($out, $rs) = &firewalld::direct_rule($action, {
|
||||
'permanent' => 1,
|
||||
'rule' => &get_firewalld_rule('ipv4', $chain,
|
||||
$direction, $iface),
|
||||
});
|
||||
return $out if ($rs);
|
||||
}
|
||||
}
|
||||
if ($ip_families{ipv6}) {
|
||||
foreach my $direction (@directions) {
|
||||
next if ($conflicting->($chain, $direction));
|
||||
my ($out, $rs) = &firewalld::direct_rule($action, {
|
||||
'permanent' => 1,
|
||||
'rule' => &get_firewalld_rule('ipv6', $chain,
|
||||
$direction, $iface),
|
||||
});
|
||||
return $out if ($rs);
|
||||
}
|
||||
}
|
||||
}
|
||||
return &firewalld::apply_firewalld();
|
||||
}
|
||||
|
||||
# setup_firewalld_rules(iface)
|
||||
# If any FirewallD rules are missing, add them
|
||||
sub setup_firewalld_rules
|
||||
{
|
||||
my $iface = shift // $config{'iface'};
|
||||
return &firewalld_rules_control('add', $iface);
|
||||
}
|
||||
|
||||
# delete_firewalld_rules()
|
||||
# Delete firewall rules for bandwidth logging
|
||||
sub delete_firewalld_rules
|
||||
{
|
||||
my $iface = shift // $config{'iface'};
|
||||
return &firewalld_rules_control('remove', $iface);
|
||||
}
|
||||
|
||||
# process_firewalld_line(line, &hours, time-now)
|
||||
# Process an IPtables firewall line, and returns 1 if successful
|
||||
sub process_firewalld_line
|
||||
{
|
||||
return &process_firewall_line(@_);
|
||||
}
|
||||
|
||||
# get_firewalld_loglevel()
|
||||
sub get_firewalld_loglevel
|
||||
{
|
||||
return ( "kern.=debug" );
|
||||
}
|
||||
|
||||
############### functions for IPtables #################
|
||||
|
||||
# check_firewall_rules()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
firewall_system=Firewall type,4,firewall-IPtables,ipfw-IPFW,ipfilter-IPFilter,shorewall-Shorewall,-Detect automatically
|
||||
firewall_system=Firewall type,4,-Detect automatically,firewalld-Firewalld,firewall-IPtables,ipfw-IPFW,ipfilter-IPFilter,shorewall-Shorewall
|
||||
bandwidth_log=Log file to create for firewall messages,0
|
||||
bandwidth_dir=Directory for bandwidth data,3,Default (/etc/webmin/bandwidth/hours)
|
||||
|
||||
@@ -34,7 +34,7 @@ foreach $m (split(/\s+/, $module_info{'depends'})) {
|
||||
}
|
||||
|
||||
# Make sure one of the syslog modules works
|
||||
if (!$syslog_module) {
|
||||
if (!$syslog_module && !$syslog_journald) {
|
||||
&ui_print_header(undef, $text{'index_title'}, "", "intro",
|
||||
1, 1);
|
||||
&ui_print_endpage(&text('index_esyslog'));
|
||||
@@ -66,16 +66,20 @@ else {
|
||||
1, 1, 0, undef, undef, undef,
|
||||
&text('index_firesys',
|
||||
$text{'system_'.$config{'firewall_system'}},
|
||||
$text{'syslog_'.$syslog_module}));
|
||||
$text{'syslog_'.($syslog_module || $syslog_journald)}));
|
||||
|
||||
# Make sure the needed firewall rules and syslog entry are in place
|
||||
$missingrule = !&check_rules();
|
||||
if ($syslog_module eq "syslog") {
|
||||
if ($syslog_journald) {
|
||||
# Systemd journal
|
||||
$sysconf = 1; # nothing to do
|
||||
}
|
||||
elsif ($syslog_module eq "syslog") {
|
||||
# Normal syslog
|
||||
$conf = &syslog::get_config();
|
||||
$sysconf = &find_sysconf($conf);
|
||||
}
|
||||
else {
|
||||
elsif ($syslog_module eq "syslog-ng") {
|
||||
# Syslog-ng
|
||||
$conf = &syslog_ng::get_config();
|
||||
($ngdest, $ngfilter, $nglog) = &find_sysconf_ng($conf);
|
||||
@@ -374,10 +378,13 @@ if (@hours) {
|
||||
push(@cols, $k);
|
||||
}
|
||||
my $bar = sprintf
|
||||
"<img src=images/blue.gif width=%d height=10>",
|
||||
"<span style='display: flex;'>".
|
||||
"<img src=images/blue.gif width=%d% ".
|
||||
"height=10>",
|
||||
$max ? int($width * $icount{$k}/$max)+1 : 1;
|
||||
$bar .= sprintf
|
||||
"<img src=images/red.gif width=%d height=10>",
|
||||
"<img src=images/red.gif width=%d% ".
|
||||
"height=10></span>",
|
||||
$max ? int($width * $ocount{$k}/$max)+1 : 1;
|
||||
push(@cols, $bar);
|
||||
push(@cols, &nice_size($icount{$k}),
|
||||
|
||||
@@ -4,14 +4,14 @@ index_efiresys2=The configured $1 firewall system was not found on your system.
|
||||
index_elog=The file $1 used for bandwidth logging is actually a directory on your system. Adjust the <a href='$2'>module configuration</a> to use a different path.
|
||||
index_edir=The directory for storing bandwidth data $1 does not exist, or is not a directory. Adjust the <a href='$2'>module configuration</a> to use a different path.
|
||||
index_emod=The Webmin module $1 is not installed on this system or is not supported by your OS. The Bandwidth Monitoring module cannot operate without it.
|
||||
index_esyslog=Neither of the System Logs modules are installed on this system and supported by your OS. The Bandwidth Monitoring module cannot operate without one of them.
|
||||
index_firesys=Using $1 firewall and $2
|
||||
index_esyslog=None of the supported system logging systems, such as Journald, Rsyslog, or others, are installed or supported by your OS. This module requires at least one of them to operate correctly.
|
||||
index_firesys=Using $1 with $2
|
||||
index_setupcannot=However, you do not have permissions to set it up!
|
||||
index_setupdesc=Before this module can report network usage, it needs to be set up to monitor traffic on the chosen network interface.
|
||||
index_setupdesc2=This module will log <em>all</em> network traffic sent or received on the selected interface, which can consume a large amount of disk space and CPU time on a fast network connection.
|
||||
index_missing3=Several firewall rules must be added, and a syslog configuration entry created.
|
||||
index_missing2=Several firewall rules must be added.
|
||||
index_missing1=A syslog configuration entry must be created.
|
||||
index_missing3=Several firewall rules need to be added, along with a configuration entry for the system logging system.
|
||||
index_missing2=Several firewall rules need to be added.
|
||||
index_missing1=A configuration entry for the system logging system must be created.
|
||||
index_iface=Chosen network interface
|
||||
index_other=Other..
|
||||
index_setup=Setup Now
|
||||
@@ -55,7 +55,7 @@ index_low=Server ports only?
|
||||
index_resolv=Resolve hostnames?
|
||||
index_nomatch=No traffic matched the selected criteria.
|
||||
index_turnoff=Turn Off Monitoring
|
||||
index_turnoffdesc=Click this button to remove the firewall rules, syslog configuration and Cron job used for bandwidth monitoring. All existing collected data will remain untouched.
|
||||
index_turnoffdesc=Click this button to remove the firewall rules, related system logging configuration, and Cron job used for bandwidth monitoring. The existing collected data will remain intact.
|
||||
index_rotate=Update Statistics
|
||||
index_rotatedesc=Click this button to process all logged network traffic up to the current time, making it immediately available for reporting.
|
||||
index_eiptables=Warning - Your IPtables configuration has an error : $1. Setting up bandwidth monitoring will clear all firewall rules.
|
||||
@@ -73,16 +73,19 @@ setup_ecannot=You are not allowed to enable monitoring
|
||||
setup_eiface=Missing or invalid interface name
|
||||
setup_ezone=Failed to find Shorewall zone for the selected interface
|
||||
|
||||
system_firewalld=Firewalld
|
||||
system_firewall=IPtables
|
||||
system_ipfw=IPFW
|
||||
system_ipfilter=IPFilter
|
||||
system_shorewall=Shorewall
|
||||
|
||||
syslog_journald=Journald
|
||||
syslog_syslog=Syslog
|
||||
syslog_syslog-ng=Syslog-NG
|
||||
|
||||
rotate_title=Updating Statistics
|
||||
rotate_doing=Processing logged network traffic ..
|
||||
rotate_done=.. done.
|
||||
rotate_done=.. done
|
||||
rotate_failed=.. failed : $1
|
||||
|
||||
__norefs=1
|
||||
|
||||
@@ -2,18 +2,11 @@
|
||||
# Run rotate.pl now
|
||||
|
||||
require './bandwidth-lib.pl';
|
||||
&ui_print_header(undef, $text{'rotate_title'}, "");
|
||||
|
||||
print "<b>$text{'rotate_doing'}</b>\n";
|
||||
print "<pre>";
|
||||
open(OUT, "$cron_cmd 2>&1 |");
|
||||
while(<OUT>) {
|
||||
print &html_escape($_);
|
||||
}
|
||||
close(OUT);
|
||||
print "</pre>\n";
|
||||
print "<b>$text{'rotate_done'}</b><p>\n";
|
||||
|
||||
&ui_print_unbuffered_header(undef, $text{'rotate_title'}, "");
|
||||
print &ui_text_wrap($text{'rotate_doing'});
|
||||
my ($out) = &backquote_logged("$cron_cmd 2>&1");
|
||||
$out = $out ? &text('rotate_failed', &html_strip($out)) : $text{'rotate_done'};
|
||||
print &ui_text_wrap("<br>$out");
|
||||
&webmin_log("rotate");
|
||||
&ui_print_footer("", $text{'index_return'});
|
||||
|
||||
|
||||
@@ -2,45 +2,74 @@
|
||||
# Parse the firewall log and rotate it
|
||||
|
||||
$no_acl_check++;
|
||||
require './bandwidth-lib.pl';
|
||||
use Time::Local;
|
||||
require './bandwidth-lib.pl';
|
||||
|
||||
our (%config, $module_config_file, $module_var_directory, $pid_file,
|
||||
$syslog_module, $syslog_journald, $bandwidth_log);
|
||||
|
||||
my ($logfh, $timestamp_file, $lastline);
|
||||
|
||||
# Detect firewall system if needed
|
||||
if (!$config{'firewall_system'}) {
|
||||
$sys = &detect_firewall_system();
|
||||
my $sys = &detect_firewall_system();
|
||||
if ($sys) {
|
||||
$config{'firewall_system'} = $sys;
|
||||
&lock_file($module_config_file);
|
||||
&save_module_config();
|
||||
&unlock_file($module_config_file);
|
||||
}
|
||||
else {
|
||||
die "Failed to detect firewall system!";
|
||||
die("Failed to detect firewall system!\n");
|
||||
}
|
||||
}
|
||||
|
||||
# See if this process is already running
|
||||
if ($pid = &check_pid_file($pid_file)) {
|
||||
if (my $pid = &check_pid_file($pid_file)) {
|
||||
print STDERR "rotate.pl process $pid is already running\n";
|
||||
exit;
|
||||
exit(1);
|
||||
}
|
||||
open(PID, ">$pid_file");
|
||||
print PID $$,"\n";
|
||||
close(PID);
|
||||
open(my $pid, ">$pid_file");
|
||||
print $pid $$,"\n";
|
||||
close($pid);
|
||||
|
||||
$time_now = time();
|
||||
@time_now = localtime($time_now);
|
||||
@hours = ( );
|
||||
# Get the current time
|
||||
my $time_now = time();
|
||||
my @time_now = localtime($time_now);
|
||||
my @hours = ( );
|
||||
|
||||
# Pre-process command
|
||||
&pre_process();
|
||||
|
||||
# Open the log file or pipe to journalctl
|
||||
if ($syslog_journald) {
|
||||
$timestamp_file = "$module_var_directory/last-processed";
|
||||
my $last_processed = 0;
|
||||
if (-r $timestamp_file) {
|
||||
$last_processed = &read_file_contents($timestamp_file);
|
||||
chomp($last_processed);
|
||||
$last_processed = int($last_processed) || 0;
|
||||
}
|
||||
my $journal_cmd = &has_command("journalctl");
|
||||
$journal_cmd = "$journal_cmd -k --since=\@$last_processed ".
|
||||
"--until=\@$time_now --grep=\"BANDWIDTH_(IN|OUT):\"";
|
||||
open($logfh, '-|', $journal_cmd) ||
|
||||
die("Cannot open $journal_cmd pipe: $!\n");
|
||||
}
|
||||
else {
|
||||
open($logfh, "<".$bandwidth_log) ||
|
||||
die("Cannot open $bandwidth_log: $!\n");
|
||||
}
|
||||
|
||||
# Scan the entries in the log file
|
||||
&pre_process();
|
||||
open(LOG, "<".$bandwidth_log);
|
||||
while(<LOG>) {
|
||||
while(<$logfh>) {
|
||||
if (&process_line($_, \@hours, $time_now)) {
|
||||
# Found a valid line
|
||||
$lastline = $_;
|
||||
}
|
||||
elsif (/last\s+message\s+repeated\s+(\d+)/) {
|
||||
# re-process the last line N-1 times
|
||||
for($i=0; $i<$1-1; $i++) {
|
||||
for(my $i=0; $i<$1-1; $i++) {
|
||||
&process_line($lastline, \@hours, $time_now);
|
||||
}
|
||||
}
|
||||
@@ -48,20 +77,29 @@ while(<LOG>) {
|
||||
#print "skipping $_";
|
||||
}
|
||||
}
|
||||
close(LOG);
|
||||
close($logfh);
|
||||
|
||||
# Save all hours
|
||||
foreach $hour (@hours) {
|
||||
foreach my $hour (@hours) {
|
||||
&save_hour($hour);
|
||||
}
|
||||
|
||||
# Truncate the file (if it exists) and notify syslog
|
||||
if (-r $bandwidth_log) {
|
||||
open(LOG, ">".$bandwidth_log);
|
||||
close(LOG);
|
||||
open(my $log, ">".$bandwidth_log);
|
||||
close($log);
|
||||
}
|
||||
&foreign_call($syslog_module, "signal_syslog") if (!$syslog_journald);
|
||||
|
||||
# Save last collection time to start from here next time
|
||||
if ($syslog_journald && @hours) {
|
||||
&lock_file($timestamp_file);
|
||||
&write_file_contents($timestamp_file, $time_now);
|
||||
&unlock_file($timestamp_file);
|
||||
}
|
||||
&foreign_call($syslog_module, "signal_syslog");
|
||||
|
||||
# Remove PID file
|
||||
unlink($pid_file);
|
||||
|
||||
# Exit with success
|
||||
exit(0);
|
||||
@@ -13,7 +13,11 @@ $iface =~ /^\S+$/ || &error($text{'setup_eiface'});
|
||||
$err = &setup_rules($iface);
|
||||
&error($err) if ($err);
|
||||
|
||||
if ($syslog_module eq "syslog") {
|
||||
if ($syslog_journald) {
|
||||
# Systemd journal
|
||||
# No setup needed
|
||||
}
|
||||
elsif ($syslog_module eq "syslog") {
|
||||
# Add syslog entry
|
||||
$conf = &syslog::get_config();
|
||||
$sysconf = &find_sysconf($conf);
|
||||
@@ -32,7 +36,7 @@ if ($syslog_module eq "syslog") {
|
||||
&error($err) if ($err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
elsif ($syslog_module eq "syslog-ng") {
|
||||
# Add syslog-ng entry
|
||||
$conf = &syslog_ng::get_config();
|
||||
($dest, $filter, $log) = &find_sysconf_ng($conf);
|
||||
|
||||
@@ -9,7 +9,11 @@ $access{'setup'} || &error($text{'turnoff_ecannot'});
|
||||
$err = &delete_rules();
|
||||
&error($err) if ($err);
|
||||
|
||||
if ($syslog_module eq "syslog") {
|
||||
if ($syslog_journald) {
|
||||
# Systemd journal
|
||||
# Nothing to do
|
||||
}
|
||||
elsif ($syslog_module eq "syslog") {
|
||||
# Remove syslog entry
|
||||
$conf = &syslog::get_config();
|
||||
$sysconf = &find_sysconf($conf);
|
||||
@@ -21,7 +25,7 @@ if ($syslog_module eq "syslog") {
|
||||
&error($err) if ($err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
elsif ($syslog_module eq "syslog-ng") {
|
||||
# Remove syslog-ng entries
|
||||
$conf = &syslog_ng::get_config();
|
||||
($dest, $filter, $log) = &find_sysconf_ng($conf);
|
||||
|
||||
Reference in New Issue
Block a user