all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: Christian Ebner <c.ebner@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [pve-devel] [PATCH common 1/1] tools: Add callback based filtering for firewall logfiles
Date: Tue, 10 Jan 2023 13:36:21 +0100	[thread overview]
Message-ID: <20230110123621.itumnpojjfjolfq3@fwblub> (raw)
In-Reply-To: <20230109150706.446377-3-c.ebner@proxmox.com>

On Mon, Jan 09, 2023 at 04:07:06PM +0100, Christian Ebner wrote:
> This patch introduces callback based filtering functionality for firewall logs.
> In addition, the contents of rotated logfiles are included by setting the
> `include_rotated_logs` flag.
> 
> Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
> ---
>  src/PVE/Tools.pm | 108 +++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 91 insertions(+), 17 deletions(-)
> 
> diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm
> index cdbee6d..cafc2f7 100644
> --- a/src/PVE/Tools.pm
> +++ b/src/PVE/Tools.pm
> @@ -17,6 +17,7 @@ use IO::Handle;
>  use IO::Pipe;
>  use IO::Select;
>  use IO::Socket::IP;
> +use IO::Zlib;
>  use IPC::Open3;
>  use JSON;
>  use POSIX qw(EINTR EEXIST EOPNOTSUPP);
> @@ -1265,29 +1266,19 @@ sub split_args {
>      return $str ? [ Text::ParseWords::shellwords($str) ] : [];
>  }
>  
> -sub dump_logfile {
> -    my ($filename, $start, $limit, $filter) = @_;
> -
> -    my $lines = [];
> -    my $count = 0;
> -
> -    my $fh = IO::File->new($filename, "r");
> -    if (!$fh) {
> -	$count++;
> -	push @$lines, { n => $count, t => "unable to open file - $!"};
> -	return ($count, $lines);
> -    }
> +sub dump_logfile_by_filehandle {
> +    my ($fh, $start, $limit, $filter, $count, $lines, $read_until_end) = @_;

^ I think it'll be easier if we move start, limit, count and lines into
a `$state` hash, and keep $read_until_end internal here.
This way we don't need to pass them back and forth via paramters &
return values.

We can pull them out as needed here and also do the defaults, eg.

    my $limit = ($state->{limit} //= 50);
    my $count = ($state->{count} //= 0);
    my $lines = ($state->{lines} //= []);
    my $read_until_end = $limit == 0;

and for $start I think it should only hit the first file anyway:

    my $start = delete($state->{start}) // 0;

>  
> -    $start = $start // 0;
> -    $limit = $limit // 50;
> -
> -    my $read_until_end = $limit == 0;
>      my $line;
>  
>      if ($filter) {
>  	# duplicate code, so that we do not slow down normal path
>  	while (defined($line = <$fh>)) {
> -	    next if $line !~ m/$filter/;
> +	    if (ref($filter) eq 'CODE') {
> +		next if !$filter->($line);
> +	    } else {
> +		next if $line !~ m/$filter/;
> +	    }
>  	    next if $count++ < $start;
>  	    if (!$read_until_end) {
>  		next if $limit <= 0;
> @@ -1308,6 +1299,29 @@ sub dump_logfile {
>  	}
>      }

and then fill the numbers back into the hash here and return nothing.
($lines would be a reference, so no need to write it back)
>  
> +    return ($count, $lines, $limit);
> +}
> +
> +sub dump_logfile {
> +    my ($filename, $start, $limit, $filter) = @_;
> +
> +    $start = $start // 0;
> +    $limit = $limit // 50;
> +
> +    my $read_until_end = $limit == 0;
> +    my $lines = [];
> +    my $count = 0;

^ then the above won't be needed

> +
> +    my $fh = IO::File->new($filename, "r");
> +    if (!$fh) {
> +	$count++;
> +	push @$lines, { n => $count, t => "unable to open file - $!"};
> +	return ($count, $lines);

^ this doesn't really need $count or $lines since they're 0 and [], so
this could just do
    return (1, [{ n => 1, t => "..."}]);
directly

> +    }
> +
> +    ($count, $lines, $limit) = dump_logfile_by_filehandle(
> +	$fh, $start, $limit, $filter, $count, $lines, $read_until_end);
> +
>      close($fh);
>  
>      # HACK: ExtJS store.guaranteeRange() does not like empty array

^ Maybe the hack part should happen in `dump_logfile_by_filehandle` by
passing a `$final` parameter to?

> @@ -1320,6 +1334,66 @@ sub dump_logfile {
>      return ($count, $lines);
>  }
>  
> +sub dump_fw_logfile {

Because then *this* one here an actually go into pve-firewall and also
drop the start/limit default logic.

> +    my ($filename, $start, $limit, $filter, $include_rotated_logs) = @_;
> +
> +    if (!$include_rotated_logs) {
> +	return dump_logfile($filename, $start, $limit, $filter);
> +    }
> +
> +    $start = $start // 0;
> +    $limit = $limit // 50;
> +
> +    # Take into consideration also rotated logs                                              

^ trailing whitespace

> +    my ($basename, $logdir, $type) = fileparse($filename);
> +    my @files = ();                                                                          

^ trailing whitespace

> +
> +    opendir(LOGDIR, $logdir) || die "Cannot open $logdir";
> +    my $entry;
> +    while ($entry = readdir(LOGDIR)) {
> +	my $namefilter = $basename."*";

^ This regex looks wrong (glob vs regex?) and should be built outside
the loop as it stays the same

Maybe our `dir_glob_foreach()` can shorten this whole block?

> +	next if $entry !~ m/$namefilter/;
> +	push @files, $entry;
> +    }
> +    closedir(LOGDIR);
> +    @files = reverse sort @files;
> +
> +    my $read_until_end = $limit == 0;
> +    my $lines = [];
> +    my $count = 0;
> +
> +    foreach (@files) {

IMO a bit too large to use $_.
Use
    for my $filename (@$files) {

> +	my ($base, $path, $type) = fileparse($_, ".gz");

You use neither $base nor $path, a simple `if ($filename =~ /\.gz$/) {`
should suffice.

> +
> +	my $fh;
> +	if ($type eq '.gz') {
> +	    $fh = IO::Zlib->new($logdir.$_, "r");
> +	} else {
> +	    $fh = IO::File->new($logdir.$_, "r");
> +	}
> +
> +	if (!$fh) {
> +	    $count++;
> +	    push @$lines, { n => $count, t => "unable to open file - $!"};

I'd like this error to only happen if the error isn't ENOENT, as that
would just be a TOCTOU race against a cleanup.

> +	    return ($count, $lines);

^ And just break out of the loop instead of returning.

> +	}
> +
> +	($count, $lines, $limit) = dump_logfile_by_filehandle(
> +	    $fh, $start, $limit, $filter, $count, $lines, $read_until_end);
> +
> +	close($fh);
> +    }
> +
> +    # HACK: ExtJS store.guaranteeRange() does not like empty array
> +    # so we add a line
> +    if (!$count) {
> +	$count++;
> +	push @$lines, { n => $count, t => "no content"};
> +    }
> +
> +    return ($count, $lines);
> +}
> +
>  sub dump_journal {
>      my ($start, $limit, $since, $until, $service) = @_;
>  
> -- 
> 2.30.2




  reply	other threads:[~2023-01-10 12:36 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-09 15:07 [pve-devel] [PATCH common firewall] Optonal `since` and `until` firewall log filtering Christian Ebner
2023-01-09 15:07 ` [pve-devel] [PATCH firewall 1/1] api: Add optional parameters `since` and `until` for timestamp filter Christian Ebner
2023-01-09 15:07 ` [pve-devel] [PATCH common 1/1] tools: Add callback based filtering for firewall logfiles Christian Ebner
2023-01-10 12:36   ` Wolfgang Bumiller [this message]
2023-01-11  8:36     ` Christian Ebner
2023-01-11  8:42       ` Wolfgang Bumiller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230110123621.itumnpojjfjolfq3@fwblub \
    --to=w.bumiller@proxmox.com \
    --cc=c.ebner@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal