diff --git a/Shorewall/Perl/Shorewall/Accounting.pm b/Shorewall/Perl/Shorewall/Accounting.pm index 110f2478d..be7ec07a8 100644 --- a/Shorewall/Perl/Shorewall/Accounting.pm +++ b/Shorewall/Perl/Shorewall/Accounting.pm @@ -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"; + } } } } diff --git a/Shorewall/Perl/Shorewall/Chains.pm b/Shorewall/Perl/Shorewall/Chains.pm index 2727f49b5..35f024bc3 100644 --- a/Shorewall/Perl/Shorewall/Chains.pm +++ b/Shorewall/Perl/Shorewall/Chains.pm @@ -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 => { => , => , ... } # blacklist => ( 0 or 1 ) # action => +# restrictions => Logical OR of restrictions in this chain. # } , # => ... # } @@ -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 { diff --git a/Shorewall/Perl/Shorewall/Config.pm b/Shorewall/Perl/Shorewall/Config.pm index e4b060c85..43f8dbf9f 100644 --- a/Shorewall/Perl/Shorewall/Config.pm +++ b/Shorewall/Perl/Shorewall/Config.pm @@ -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' , ''; diff --git a/Shorewall/changelog.txt b/Shorewall/changelog.txt index b9e601b3b..85ab1ddb9 100644 --- a/Shorewall/changelog.txt +++ b/Shorewall/changelog.txt @@ -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. diff --git a/Shorewall/releasenotes.txt b/Shorewall/releasenotes.txt index 83420d2ac..59e1b4409 100644 --- a/Shorewall/releasenotes.txt +++ b/Shorewall/releasenotes.txt @@ -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 ---------------------------------------------------------------------------- diff --git a/docs/Accounting.xml b/docs/Accounting.xml index bec8bc483..61c4524d7 100644 --- a/docs/Accounting.xml +++ b/docs/Accounting.xml @@ -235,6 +235,177 @@ +
+ Sectioned Accounting Rules + + 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. Section headers + have the form: + + + section-name + + 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 + + + + accounout 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. + + + + Here is a sample sectioned file that used Per-IP Accounting. + + + In this example, the dmz net corresponds to a vserver zone so + lives on the firewall itself. + + + #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 + +
+
Integrating Shorewall Accounting with Collectd