Section the accounting file

This commit is contained in:
Tom Eastep 2011-02-12 12:47:15 -08:00
parent 195903444d
commit 5c0b592934
6 changed files with 408 additions and 103 deletions

View File

@ -42,13 +42,63 @@ our $VERSION = '4.4.17';
#
our %tables;
our $jumpchainref;
our %accountingjumps;
our $asection;
our $defaultchain;
our $defaultrestriction;
our $restriction;
our $accounting_commands = { COMMENT => 0, SECTION => 2 };
our $sectionname;
use constant {
LEGACY => -1,
INPUT => 1,
OUTPUT => 2,
FORWARD => 3 };
our %asections = ( INPUT => INPUT,
FORWARD => FORWARD,
OUTPUT => OUTPUT );
#
# Called by the compiler to [re-]initialize this module's state
#
sub initialize() {
our $jumpchainref = undef;
%tables = ();
our %accountingjumps = ();
$jumpchainref = undef;
%tables = ();
%accountingjumps = ();
$asection = LEGACY;
$defaultchain = 'accounting';
$defaultrestriction = NO_RESTRICT;
$sectionname = '';
}
#
# Process a SECTION header
#
sub process_section ($) {
$sectionname = shift;
my $newsect = $asections{$sectionname};
#
# read_a_line has already verified that there are exactly two tokens on the line
#
fatal_error "Invalid SECTION ($sectionname)" unless defined $newsect;
fatal_error "SECTION not allowed after un-sectioned rules" unless $asection;
fatal_error "Duplicate or out-of-order SECTION ($sectionname)" if $newsect <= $asection;
if ( $sectionname eq 'INPUT' ) {
$defaultchain = 'accountin';
$defaultrestriction = INPUT_RESTRICT;
} elsif ( $sectionname eq 'OUTPUT' ) {
$defaultchain = 'accountout';
$defaultrestriction = OUTPUT_RESTRICT;
} else {
$defaultchain = 'accounting';
$defaultrestriction = NO_RESTRICT;
}
$asection = $newsect;
}
#
@ -59,17 +109,24 @@ sub process_accounting_rule( ) {
our $jumpchainref = 0;
our %accountingjumps;
my ($action, $chain, $source, $dest, $proto, $ports, $sports, $user, $mark, $ipsec, $headers ) = split_line1 1, 11, 'Accounting File';
my ($action, $chain, $source, $dest, $proto, $ports, $sports, $user, $mark, $ipsec, $headers ) = split_line1 1, 11, 'Accounting File', $accounting_commands;
if ( $action eq 'COMMENT' ) {
process_comment;
return 0;
}
if ( $action eq 'SECTION' ) {
process_section( $chain );
return 0;
}
$asection = 0 if $asection == LEGACY;
our $disposition = '';
sub reserved_chain_name($) {
$_[0] =~ /^acc(?:ount(?:ing|out)|ipsecin|ipsecout)$/;
$_[0] =~ /^acc(?:ount(?:in|ing|out)|ipsecin|ipsecout)$/;
}
sub ipsec_chain_name($) {
@ -90,7 +147,7 @@ sub process_accounting_rule( ) {
sub jump_to_chain( $ ) {
my $jumpchain = $_[0];
fatal_error "Jumps to the $jumpchain chain are not allowed" if reserved_chain_name( $jumpchain );
$jumpchainref = ensure_accounting_chain( $jumpchain, 0 );
$jumpchainref = ensure_accounting_chain( $jumpchain, 0, $restriction );
check_chain( $jumpchainref );
$disposition = $jumpchain;
$jumpchain;
@ -148,20 +205,21 @@ sub process_accounting_rule( ) {
}
}
my $restriction = NO_RESTRICT;
$restriction = $defaultrestriction;
if ( $source eq 'any' || $source eq 'all' ) {
$source = ALLIP;
$source = ALLIP;
} else {
fatal_error "MAC addresses are not allowed in the accounting file" if $source =~ /~/;
$restriction |= INPUT_RESTRICT if $source =~ /~/;
}
if ( have_bridges ) {
if ( have_bridges && ! $asection ) {
my $fw = firewall_zone;
if ( $source =~ /^$fw:?(.*)$/ ) {
$source = $1 ? $1 : ALLIP;
$restriction = OUTPUT_RESTRICT;
fatal_error "MAC addresses are not allowed in an unsectioned accounting file" if $restriction & OUTPUT || $source =~ /~/;
$chain = 'accountout' unless $chain and $chain ne '-';
$dest = ALLIP if $dest eq 'any' || $dest eq 'all';
} else {
@ -181,7 +239,7 @@ sub process_accounting_rule( ) {
}
}
} else {
$chain = 'accounting' unless $chain and $chain ne '-';
$chain = $defaultchain unless $chain and $chain ne '-';
$dest = ALLIP if $dest eq 'any' || $dest eq 'all';
}
@ -189,7 +247,15 @@ sub process_accounting_rule( ) {
my $dir;
if ( ! $chainref ) {
$chainref = ensure_accounting_chain $chain, 0;
if ( reserved_chain_name( $chain ) ) {
fatal_error "May not use chain $chain in the $sectionname section" if $asection && $chain ne $defaultchain;
$chainref = ensure_accounting_chain $chain, 0 , $restriction;
} elsif ( $asection ) {
fatal_error "Unknown accounting chain ($chain)";
} else {
$chainref = ensure_accounting_chain $chain, 0 , $restriction;
}
$dir = ipsec_chain_name( $chain );
if ( $ipsec ne '-' ) {
@ -209,11 +275,18 @@ sub process_accounting_rule( ) {
$rule .= do_ipsec( $dir , $ipsec );
}
$accountingjumps{$jumpchainref->{name}}{$chain} = 1 if $jumpchainref;
if ( $jumpchainref ) {
if ( $asection ) {
my $jumprestrict = $jumpchainref->{restriction} || $restriction;
fatal_error "Chain $jumpchainref->{name} contains rules that are incompatible with the $sectionname section" if $restriction && $jumprestrict ne $restriction;
}
$accountingjumps{$jumpchainref->{name}}{$chain} = 1;
}
fatal_error "$chain is not an accounting chain" unless $chainref->{accounting};
$restriction = $dir eq 'in' ? INPUT_RESTRICT : OUTPUT_RESTRICT if $dir;
$restriction = $dir eq 'in' ? INPUT_RESTRICT : OUTPUT_RESTRICT if $dir && ! $asection;
expand_rule
$chainref ,
@ -274,59 +347,66 @@ sub setup_accounting() {
clear_comment;
if ( have_bridges ) {
if ( $filter_table->{accounting} ) {
for my $chain ( qw/INPUT FORWARD/ ) {
if ( $nonEmpty ) {
if ( have_bridges || $asection ) {
if ( $filter_table->{accountin} ) {
add_jump( $filter_table->{INPUT}, 'accountin', 0, '', 0, 0 );
}
if ( $filter_table->{accounting} ) {
optimize_okay( 'accounting' ) if $section;
for my $chain ( qw/INPUT FORWARD/ ) {
add_jump( $filter_table->{$chain}, 'accounting', 0, '', 0, 0 );
}
}
if ( $filter_table->{accountout} ) {
add_jump( $filter_table->{OUTPUT}, 'accountout', 0, '', 0, 0 );
}
} elsif ( $filter_table->{accounting} ) {
for my $chain ( qw/INPUT FORWARD OUTPUT/ ) {
add_jump( $filter_table->{$chain}, 'accounting', 0, '', 0, 0 );
}
}
if ( $filter_table->{accountout} ) {
add_jump( $filter_table->{OUTPUT}, 'accountout', 0, '', 0, 0 );
}
} elsif ( $filter_table->{accounting} ) {
for my $chain ( qw/INPUT FORWARD OUTPUT/ ) {
add_jump( $filter_table->{$chain}, 'accounting', 0, '', 0, 0 );
}
}
if ( $filter_table->{accipsecin} ) {
for my $chain ( qw/INPUT FORWARD/ ) {
add_jump( $filter_table->{$chain}, 'accipsecin', 0, '', 0, 0 );
}
}
if ( $filter_table->{accipsecout} ) {
for my $chain ( qw/FORWARD OUTPUT/ ) {
add_jump( $filter_table->{$chain}, 'accipsecout', 0, '', 0, 0 );
}
}
for ( accounting_chainrefs ) {
warning_message "Accounting chain $_->{name} has no references" unless keys %{$_->{references}};
}
if ( my $chainswithjumps = keys %accountingjumps ) {
my $progress = 1;
while ( $chainswithjumps && $progress ) {
$progress = 0;
for my $chain1 ( keys %accountingjumps ) {
if ( keys %{$accountingjumps{$chain1}} ) {
for my $chain2 ( keys %{$accountingjumps{$chain1}} ) {
delete $accountingjumps{$chain1}{$chain2}, $progress = 1 unless $accountingjumps{$chain2};
}
} else {
delete $accountingjumps{$chain1};
$chainswithjumps--;
$progress = 1;
}
if ( $filter_table->{accipsecin} ) {
for my $chain ( qw/INPUT FORWARD/ ) {
add_jump( $filter_table->{$chain}, 'accipsecin', 0, '', 0, 0 );
}
}
if ( $chainswithjumps ) {
my @chainswithjumps = keys %accountingjumps;
fatal_error "Jump loop involving the following chains: @chainswithjumps";
if ( $filter_table->{accipsecout} ) {
for my $chain ( qw/FORWARD OUTPUT/ ) {
add_jump( $filter_table->{$chain}, 'accipsecout', 0, '', 0, 0 );
}
}
for ( accounting_chainrefs ) {
warning_message "Accounting chain $_->{name} has no references" unless keys %{$_->{references}};
}
if ( my $chainswithjumps = keys %accountingjumps ) {
my $progress = 1;
while ( $chainswithjumps && $progress ) {
$progress = 0;
for my $chain1 ( keys %accountingjumps ) {
if ( keys %{$accountingjumps{$chain1}} ) {
for my $chain2 ( keys %{$accountingjumps{$chain1}} ) {
delete $accountingjumps{$chain1}{$chain2}, $progress = 1 unless $accountingjumps{$chain2};
}
} else {
delete $accountingjumps{$chain1};
$chainswithjumps--;
$progress = 1;
}
}
}
if ( $chainswithjumps ) {
my @chainswithjumps = keys %accountingjumps;
fatal_error "Jump loop involving the following chains: @chainswithjumps";
}
}
}
}

View File

@ -43,6 +43,7 @@ our @EXPORT = qw(
ensure_manual_chain
log_rule_limit
dont_optimize
optimize_okay
dont_delete
dont_move
@ -221,6 +222,7 @@ our $VERSION = '4.4_18';
# references => { <ref1> => <refs>, <ref2> => <refs>, ... }
# blacklist => <number of blacklist rules at the head of the rules array> ( 0 or 1 )
# action => <action tuple that generated this chain>
# restrictions => Logical OR of restrictions in this chain.
# } ,
# <chain2> => ...
# }
@ -1107,14 +1109,15 @@ sub new_chain($$)
assert( $chain_table{$table} && ! ( $chain_table{$table}{$chain} || $builtin_target{ $chain } ) );
my $chainref = { name => $chain,
rules => [],
table => $table,
loglevel => '',
log => 1,
cmdlevel => 0,
references => {},
blacklist => 0 };
my $chainref = { name => $chain,
rules => [],
table => $table,
loglevel => '',
log => 1,
cmdlevel => 0,
references => {},
blacklist => 0 ,
restriction => 0 };
trace( $chainref, 'N', undef, '' ) if $debug;
@ -1240,6 +1243,21 @@ sub dont_optimize( $ ) {
$chainref;
}
#
# Reverse the effect of dont_optimize
#
sub optimize_okay( $ ) {
my $chain = shift;
my $chainref = reftype $chain ? $chain : $filter_table->{$chain};
$chainref->{dont_optimize} = 0;
trace( $chainref, 'O', undef, '' ) if $debug;
$chainref;
}
#
# Set the dont_optimize and dont_delete flags for a chain
#
@ -1301,9 +1319,9 @@ sub ensure_filter_chain( $$ )
#
# Create an accounting chain if necessary and return a reference to its table entry.
#
sub ensure_accounting_chain( $$ )
sub ensure_accounting_chain( $$$ )
{
my ($chain, $ipsec) = @_;
my ($chain, $ipsec, $restriction ) = @_;
my $chainref = $filter_table->{$chain};
@ -1313,10 +1331,11 @@ sub ensure_accounting_chain( $$ )
fatal_error "Chain name ($chain) too long" if length $chain > 29;
fatal_error "Invalid Chain name ($chain)" unless $chain =~ /^[-\w]+$/;
$chainref = new_chain 'filter' , $chain;
$chainref->{accounting} = 1;
$chainref->{referenced} = 1;
$chainref->{ipsec} = $ipsec;
$chainref->{dont_optimize} = 1 unless $config{OPTIMIZE_ACCOUNTING} && $chain ne 'accounting';
$chainref->{accounting} = 1;
$chainref->{referenced} = 1;
$chainref->{restriction} = $restriction;
$chainref->{ipsec} = $ipsec;
$chainref->{dont_optimize} = 1 unless $config{OPTIMIZE_ACCOUNTING};
if ( $chain ne 'accounting' ) {
my $file = find_file $chain;
@ -3667,7 +3686,8 @@ sub expand_rule( $$$$$$$$$$;$ )
$rule .= '-s $source ';
} else {
fatal_error "Source Interface ($iiface) not allowed when the source zone is the firewall zone" if $restriction & OUTPUT_RESTRICT;
fatal_error "Source Interface ($iiface) not allowed when the SOURCE is the firewall" if $restriction & OUTPUT_RESTRICT;
$chainref->{restriction} |= $restriction;
$rule .= match_source_dev( $iiface );
}
}
@ -3752,14 +3772,15 @@ sub expand_rule( $$$$$$$$$$;$ )
$rule .= '-d $dest ';
} else {
fatal_error "Bridge Port ($diface) not allowed in OUTPUT or POSTROUTING rules" if ( $restriction & ( POSTROUTE_RESTRICT + OUTPUT_RESTRICT ) ) && port_to_bridge( $diface );
fatal_error "Destination Interface ($diface) not allowed when the destination zone is the firewall zone" if $restriction & INPUT_RESTRICT;
fatal_error "Destination Interface ($diface) not allowed when the destination zone is the firewall" if $restriction & INPUT_RESTRICT;
fatal_error "Destination Interface ($diface) not allowed in the mangle OUTPUT chain" if $restriction & DESTIFACE_DISALLOW;
if ( $iiface ) {
my $bridge = port_to_bridge( $diface );
fatal_error "Source interface ($iiface) is not a port on the same bridge as the destination interface ( $diface )" if $bridge && $bridge ne source_port_to_bridge( $iiface );
}
$chainref->{restriction} |= $restriction;
$rule .= match_dest_dev( $diface );
}
} else {

View File

@ -2042,28 +2042,6 @@ sub check_trivalue( $$ ) {
}
}
#
# Check ACCOUNTING
#
sub check_accounting() {
my $val = $config{ACCOUNTING};
if ( defined $val ) {
$val = lc $val;
if ( $val eq 'yes' || $val eq 'on' ) {
$config{ACCOUNTING} = 'Yes';
} elsif ( $val eq 'no' || $val eq 'off' ) {
$config{ACCOUNTING} = '';
} elsif ( $val eq '' ) {
$config{ACCOUNTING} = 'NG';
} elsif ( $val ne '' ) {
fatal_error "Invalid value ($val) for ACCOUNTING";
}
} else {
$config{ACCOUNTING} = 'Yes';
}
}
#
# Produce a report of the detected capabilities
#
@ -3238,9 +3216,7 @@ sub get_configuration( $ ) {
default_yes_no 'AUTOMAKE' , '';
default_yes_no 'WIDE_TC_MARKS' , '';
default_yes_no 'TRACK_PROVIDERS' , '';
check_accounting;
default_yes_no 'ACCOUNTING' , 'Yes';
default_yes_no 'OPTIMIZE_ACCOUNTING' , '';
default_yes_no 'DYNAMIC_BLACKLIST' , 'Yes';
default_yes_no 'REQUIRE_INTERFACE' , '';

View File

@ -2,6 +2,8 @@ Changes in Shorewall 4.4.18
1) Split up modules file.
2) Add sections to the accounting file.
Changes in Shorewall 4.4.17
1) Secure helper and modules files for non-root access.

View File

@ -43,6 +43,61 @@ None.
For example, if you don't use traffic shaping or ipsets, you can
remove those from your modules file.
2) Traditionally, the root of the Shorewall accounting rules has been
the 'accounting' chain. Having a single root chain has drawbacks:
- Many rules are traversed needlessly (they could not possibly
match traffic).
- At any time, the Netfilter team could begin generating errors
when loading those same rules.
- MAC addresses may not be used in the accounting rules.
- The 'accounting' chain cannot be optimized when
OPTIMIZE_ACCOUNTING=Yes.
- The rules may be defined in any order so the rules compiler must
post-process the ruleset to ensure that there are no loops and to
alert the user to unreferenced chains.
Beginning with Shorewall 4.4.18, the accounting structure can be
created with three root chains:
- accountin: Rules that are valid in the INPUT chain (may not
specify an output interface).
- accountout: Rules that are valid in the OUTPUT chain (may not
specify an input interface or a MAC address).
- accounting: Other rules.
The new structure is enabled by sectioning the accounting file in a
manner similar to the rules file.
The sections are INPUT, OUTPUT and FORWARD and must appear in that
order (although any of them may be omitted). The first
non-commentary record in the accounting file must be a section
header when sectioning is used.
When sections are enabled:
- You must jump to a user-defined accounting chain before you can
add rules to that chain. This eliminates loops and unreferenced
chains.
- You may not specify an output interface in the INPUT section.
- In the OUTPUT section:
- You may not specify an input interface
- You may not jump to a chain defined in the INPUT section that
specifies an input interface
- You may not specify a MAC address
- You may not jump to a chain defined in the INPUT section that
specifies specifies a MAC address.
- The default value of the CHAIN column is:
- 'accountin' in the INPUT section
- 'accountout' in the OUTPUT section
- 'accounting' in the FORWARD section
- Traffic addressed to the firewall goes through the rules defined
in the INPUT section.
- Traffic originating on the firewall goes through the rules
defined in the OUTPUT section.
- Traffic being forwarded through the firewall goes through the
rules from all three sections.
----------------------------------------------------------------------------
I V. R E L E A S E 4 . 4 H I G H L I G H T S
----------------------------------------------------------------------------

View File

@ -235,6 +235,177 @@
</itemizedlist>
</section>
<section>
<title>Sectioned Accounting Rules</title>
<para>Traditionally, the root of the Shorewall accounting rules has been
the <emphasis role="bold">accounting</emphasis> chain. Having a single
root chain has drawbacks:</para>
<itemizedlist>
<listitem>
<para>Many rules are traversed needlessly (they could not possibly
match traffic).</para>
</listitem>
<listitem>
<para>At any time, the Netfilter team could begin generating errors
when loading those same rules.</para>
</listitem>
<listitem>
<para>MAC addresses may not be used in the accounting rules.</para>
</listitem>
<listitem>
<para>The <emphasis role="bold">accounting</emphasis> chain cannot be
optimized when OPTIMIZE_ACCOUNTING=Yes.</para>
</listitem>
<listitem>
<para>The rules may be defined in any order so the rules compiler must
post-process the ruleset to ensure that there are no loops and to
alert the user to unreferenced chains.</para>
</listitem>
</itemizedlist>
<para> Beginning with Shorewall 4.4.18, the accounting structure can be
created with three root chains:</para>
<itemizedlist>
<listitem>
<para><emphasis role="bold">accountin</emphasis>: Rules that are valid
in the <emphasis role="bold">INPUT</emphasis> chain (may not specify
an output interface).</para>
</listitem>
<listitem>
<para><emphasis role="bold">accountout</emphasis>: Rules that are
valid in the OUTPUT chain (may not specify an input interface or a MAC
address).</para>
</listitem>
<listitem>
<para><emphasis role="bold">accounting</emphasis>: Other rules.</para>
</listitem>
</itemizedlist>
<para>The new structure is enabled by sectioning the accounting file in a
manner similar to the <ulink url="manpages/shorewall-rules.html">rules
file</ulink>. The sections are <emphasis role="bold">INPUT</emphasis>,
<emphasis role="bold">OUTPUT</emphasis> and <emphasis
role="bold">FORWARD</emphasis> and must appear in that order (although any
of them may be omitted). The first non-commentary record in the accounting
file must be a section header when sectioning is used. Section headers
have the form:</para>
<para><option>SECTION</option>
<replaceable>section-name</replaceable></para>
<para>When sections are enabled:</para>
<itemizedlist>
<listitem>
<para>You must jump to a user-defined accounting chain before you can
add rules to that chain.</para>
</listitem>
<listitem>
<para>This eliminates loops and unreferenced chains.</para>
</listitem>
<listitem>
<para>You may not specify an output interface in the <emphasis
role="bold">INPUT</emphasis> section.</para>
</listitem>
<listitem>
<para>In the OUTPUT section:</para>
<itemizedlist>
<listitem>
<para>You may not specify an input interface</para>
</listitem>
<listitem>
<para>You may not jump to a chain defined in the <emphasis
role="bold">INPUT</emphasis> section that specifies an input
interface</para>
</listitem>
<listitem>
<para>You may not specify a MAC address</para>
</listitem>
<listitem>
<para>You may not jump to a chain defined in the <emphasis
role="bold">INPUT</emphasis> section that specifies specifies a
MAC address.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>The default value of the CHAIN column is:</para>
<itemizedlist>
<listitem>
<para><emphasis role="bold">accountin</emphasis> in the <emphasis
role="bold">INPUT</emphasis> section</para>
</listitem>
<listitem>
<para><emphasis role="bold">accounout</emphasis> in the <emphasis
role="bold">OUTPUT</emphasis> section</para>
</listitem>
<listitem>
<para><emphasis role="bold">accounting</emphasis> in the <emphasis
role="bold">FORWARD</emphasis> section</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>Traffic addressed to the firewall goes through the rules defined
in the INPUT section.</para>
</listitem>
<listitem>
<para>Traffic originating on the firewall goes through the rules
defined in the OUTPUT section.</para>
</listitem>
<listitem>
<para>Traffic being forwarded through the firewall goes through the
rules from all three sections.</para>
</listitem>
</itemizedlist>
<para>Here is a sample sectioned file that used <link
linkend="perIP">Per-IP Accounting</link>.</para>
<caution>
<para>In this example, the dmz net corresponds to a vserver zone so
lives on the firewall itself.</para>
</caution>
<programlisting>#ACTION CHAIN SOURCE DESTINATION PROTO DEST SOURCE USER/ MARK IPSEC
# PORT(S) PORT(S) GROUP
SECTION INPUT
ACCOUNT(fw-net,$FW_NET) - COM_IF
ACCOUNT(dmz-net,$DMZ_NET) - COM_IF
SECTION OUTPUT
ACCOUNT(fw-net,$FW_NET) - - COM_IF
ACCOUNT(dmz-net,$DMZ_NET) - - COM_IF
SECTION FORWARD
ACCOUNT(loc-net,$INT_NET) - COM_IF INT_IF
ACCOUNT(loc-net,$INT_NET) - INT_IF COM_IF
</programlisting>
</section>
<section id="Collectd">
<title>Integrating Shorewall Accounting with Collectd</title>