mirror of
https://gitlab.com/shorewall/code.git
synced 2024-12-30 01:58:52 +01:00
42dd8dfee9
Signed-off-by: Tom Eastep <teastep@shorewall.net>
318 lines
9.1 KiB
Perl
318 lines
9.1 KiB
Perl
#
|
|
# Shorewall 4.5 -- /usr/share/shorewall/Shorewall/ARP.pm
|
|
#
|
|
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
|
|
#
|
|
# (c) 2013 - Tom Eastep (teastep@shorewall.net)
|
|
#
|
|
# Complete documentation is available at http://shorewall.net
|
|
#
|
|
# This program is part of Shorewall.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by the
|
|
# Free Software Foundation, either version 2 of the license or, at your
|
|
# option, any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# This file is responsible for Shorewall's arptables support
|
|
#
|
|
package Shorewall::ARP;
|
|
require Exporter;
|
|
|
|
use Shorewall::Config qw(:DEFAULT :internal);
|
|
use Shorewall::Zones;
|
|
use Shorewall::IPAddrs;
|
|
use strict;
|
|
|
|
our @ISA = qw(Exporter);
|
|
our @EXPORT = ( qw( process_arprules create_arptables_load preview_arptables_load ) );
|
|
|
|
our %arp_table;
|
|
our $arp_input;
|
|
our $arp_output;
|
|
our $arp_forward;
|
|
our $sourcemac;
|
|
our $destmac;
|
|
our $addrlen;
|
|
our $hw;
|
|
our @builtins;
|
|
our $arptablesjf;
|
|
our @map = ( qw( 0 Request Reply Request_Reverse Reply_Reverse DRARP_Request DRARP_Reply DRARP_Error InARP_Request ARP_NAK ) );
|
|
|
|
|
|
#
|
|
# Handles the network and mac parts of the SOURCE ($source == 1 ) and DEST ($source == 0) columns in the arprules file.
|
|
# Returns any match(es) specified.
|
|
#
|
|
sub match_arp_net( $$$ ) {
|
|
my ( $net, $mac, $source ) = @_;
|
|
|
|
my $return = '';
|
|
|
|
if ( supplied $net ) {
|
|
my $invert = ( $net =~ s/^!// ) ? '! ' : '';
|
|
validate_net $net, 0;
|
|
$return = $source ? "-s ${invert}$net " : "-d ${invert}$net ";
|
|
}
|
|
|
|
if ( supplied $mac ) {
|
|
my ( $addr , $mask ) = split( '/', $mac, 2 );
|
|
|
|
my $invert = ( $addr =~ s/^!// ) ? '! ' : '';
|
|
|
|
fatal_error "Invalid MAC address ($addr)" unless $addr =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
|
|
if ( supplied $mask ) {
|
|
fatal_error "Invalid MAC Mask ($mask)" unless $mask =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
|
|
$return .= $source ? "$sourcemac $invert$addr/$mask " : "$destmac $invert$addr/mask ";
|
|
} else {
|
|
$return .= $source ? "$sourcemac $invert$addr " : "$destmac $invert$addr ";
|
|
}
|
|
}
|
|
|
|
$return;
|
|
}
|
|
|
|
#
|
|
# Process a rule in the arprules file
|
|
#
|
|
sub process_arprule() {
|
|
my ( $originalaction, $source, $dest, $opcode ) = split_line( 'arprules file entry',
|
|
{action => 0, source => 1, dest => 2, opcode => 3 } );
|
|
|
|
my $chainref;
|
|
my $iifaceref;
|
|
my $iiface;
|
|
my $difaceref;
|
|
my $diface;
|
|
my $saddr;
|
|
my $smac;
|
|
my $daddr;
|
|
my $dmac;
|
|
my $rule = '';
|
|
|
|
fatal_error "ACTION must be specified" if $originalaction eq '-';
|
|
|
|
my ( $action, $newaddr ) = split( ':', $originalaction, 2 );
|
|
|
|
my %functions = ( DROP => sub() { $rule .= "-j DROP" },
|
|
ACCEPT => sub() { $rule .= "-j ACCEPT" },
|
|
SNAT => sub() { validate_address $newaddr, 0;
|
|
$rule .= "-j mangle --mangle-ip-s $newaddr"; },
|
|
DNAT => sub() { validate_address $newaddr, 0;
|
|
$rule .= "-j mangle --mangle-ip-d $newaddr"; },
|
|
SMAT => sub() { fatal_error "Invalid MAC address ($newaddr)" unless $newaddr =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
|
|
$rule .= "$addrlen 6 -j mangle --mangle-$hw-s $newaddr"; },
|
|
DMAT => sub() { fatal_error "Invalid MAC address ($newaddr)" unless $newaddr =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
|
|
$rule .= "$addrlen 6 -j mangle --mangle-$hw-d $newaddr"; },
|
|
SNATC => sub() { validate_address $newaddr, 0;
|
|
$rule .= "-j mangle --mangle-ip-s $newaddr --mangle-target CONTINUE"; },
|
|
DNATC => sub() { validate_address $newaddr, 0;
|
|
$rule .= "-j mangle --mangle-ip-d $newaddr --mangle-target CONTINUE"; },
|
|
SMATC => sub() { fatal_error "Invalid MAC address ($newaddr)" unless $newaddr =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
|
|
$rule .= "$addrlen 6 -j mangle --mangle-$hw-s $newaddr --mangle-target CONTINUE"; },
|
|
DMATC => sub() { fatal_error "Invalid MAC address ($newaddr)" unless $newaddr =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
|
|
$rule .= "$addrlen 6 -j mangle --mangle-$hw-d $newaddr --mangle-target CONTINUE"; },
|
|
);
|
|
|
|
if ( supplied $newaddr ) {
|
|
fatal_error "The $action ACTION does not allow a new address" unless $action =~ /^(?:SNAT|DNAT|SMAT|DMAT)C?$/;
|
|
} else {
|
|
fatal_error "The $action ACTION requires a new address" if $action =~ /^(?:SNAT|DNAT|SMAT|DMAT)C?$/;
|
|
}
|
|
|
|
my $function = $functions{$action};
|
|
|
|
fatal_error "Unknown ACTION ($action)" unless $function;
|
|
|
|
if ( $source ne '-' ) {
|
|
( $iiface, $saddr, $smac ) = split /:/, $source, 3;
|
|
|
|
fatal_error "SOURCE interface missing" unless supplied $iiface;
|
|
|
|
$iiface = ( $iifaceref = find_interface( $iiface ) )->{physical};
|
|
|
|
fatal_error "Wildcard Interfaces ( $iiface )may not be used in this context" if $iiface =~ /\+$/;
|
|
|
|
$rule .= "-i $iiface ";
|
|
$rule .= match_arp_net( $saddr , $smac, 1 ) if supplied( $saddr );
|
|
$chainref = $arp_input;
|
|
}
|
|
|
|
if ( $dest ne '-' ) {
|
|
( $diface, $daddr, $dmac ) = split /:/, $dest, 3;
|
|
|
|
fatal_error "DEST interface missing" unless supplied $diface;
|
|
|
|
$diface = ( $difaceref = find_interface( $diface ) )->{physical};
|
|
|
|
fatal_error "A wildcard interfaces ( $diface) may not be used in this context" if $diface =~ /\+$/;
|
|
|
|
if ( $iiface ) {
|
|
fatal_error "When both SOURCE and DEST are given, the interfaces must be ports on the same bridge"
|
|
if $iifaceref->{bridge} ne $difaceref->{bridge};
|
|
$chainref = $arp_forward;
|
|
} else {
|
|
$chainref = $arp_output;
|
|
}
|
|
|
|
$rule .= "-o $diface ";
|
|
$rule .= match_arp_net( $daddr , $dmac, 0 ) if supplied( $daddr );
|
|
|
|
}
|
|
|
|
if ( $opcode ne '-' ) {
|
|
my $invert = ( $opcode =~ s/^!// ) ? '! ' : '';
|
|
warning_message q(arptables versions through 0.3.4 ignore '!' after '--opcode') if $invert && ! $arptablesjf;
|
|
fatal_error "Invalid ARP OPCODE ($opcode)" unless $opcode =~ /^\d$/ && $opcode;
|
|
$rule .= $arptablesjf ? " --arpop ${invert}$map[$opcode] " : "--opcode ${invert}$opcode ";
|
|
}
|
|
|
|
$function ->();
|
|
|
|
fatal_error "Either SOURCE or DEST must be specified" unless $chainref;
|
|
|
|
push @$chainref, $rule;
|
|
|
|
}
|
|
|
|
#
|
|
# Process the arprules file -- returns true if there were any arp rules
|
|
#
|
|
sub process_arprules() {
|
|
my $result = 0;
|
|
|
|
if ( $arptablesjf = have_capability 'ARPTABLESJF' ) {
|
|
$arp_input = $arp_table{IN} = [];
|
|
$arp_output = $arp_table{OUT} = [];
|
|
$arp_forward = $arp_table{FORWARD} = [];
|
|
@builtins = qw( IN OUT FORWARD );
|
|
$sourcemac = '-z';
|
|
$destmac = '-y';
|
|
$addrlen = '--arhln';
|
|
$hw = 'hw';
|
|
} else {
|
|
$arp_input = $arp_table{INPUT} = [];
|
|
$arp_output = $arp_table{OUTPUT} = [];
|
|
$arp_forward = $arp_table{FORWARD} = [];
|
|
@builtins = qw( INPUT OUTPUT FORWARD );
|
|
$sourcemac = '--source-mac';
|
|
$destmac = '--destination-mac';
|
|
$addrlen = '--h-length';
|
|
$hw = 'mac';
|
|
}
|
|
|
|
my $fn = open_file 'arprules';
|
|
|
|
if ( $fn ) {
|
|
first_entry( sub() {
|
|
$result = 1;
|
|
progress_message2 "$doing $fn..."; }
|
|
);
|
|
process_arprule while read_a_line( NORMAL_READ );
|
|
}
|
|
|
|
$result;
|
|
}
|
|
|
|
#
|
|
# Generate the arptables_load() function
|
|
#
|
|
sub create_arptables_load( $ ) {
|
|
my $test = shift;
|
|
|
|
emit ( '#',
|
|
'# Create the input to arptables-restore and pass that input to the utility',
|
|
'#',
|
|
'setup_arptables()',
|
|
'{'
|
|
);
|
|
|
|
push_indent;
|
|
|
|
save_progress_message "Preparing arptables-restore input...";
|
|
|
|
emit '';
|
|
|
|
emit "exec 3>\${VARDIR}/.arptables-input";
|
|
|
|
my $date = localtime;
|
|
|
|
unless ( $test ) {
|
|
emit_unindented '#';
|
|
emit_unindented "# Generated by Shorewall $globals{VERSION} - $date";
|
|
emit_unindented '#';
|
|
}
|
|
|
|
emit '';
|
|
emit 'cat >&3 << __EOF__';
|
|
|
|
emit_unindented "*filter";
|
|
|
|
emit_unindented ":$_ ACCEPT" for @builtins;
|
|
|
|
for ( @builtins ) {
|
|
my $rules = $arp_table{$_};
|
|
|
|
while ( my $rule = shift @$rules ) {
|
|
emit_unindented "-A $_ $rule";
|
|
}
|
|
}
|
|
|
|
emit_unindented "COMMIT\n" if $arptablesjf;
|
|
|
|
emit_unindented "__EOF__";
|
|
|
|
#
|
|
# Now generate the actual ip[6]tables-restore command
|
|
#
|
|
emit( 'exec 3>&-',
|
|
'',
|
|
'progress_message2 "Running $ARPTABLES_RESTORE..."',
|
|
'',
|
|
'cat ${VARDIR}/.arptables-input | $ARPTABLES_RESTORE # Use this nonsensical form to appease SELinux',
|
|
'if [ $? != 0 ]; then',
|
|
qq( fatal_error "arptables-restore Failed. Input is in \${VARDIR}/.arptables-input"),
|
|
"fi\n",
|
|
"run_ip neigh flush nud stale nud reachable\n",
|
|
);
|
|
|
|
pop_indent;
|
|
emit "}\n";
|
|
}
|
|
|
|
#
|
|
# Preview the generated ARP rules
|
|
#
|
|
sub preview_arptables_load() {
|
|
|
|
my $date = localtime;
|
|
|
|
print "#\n# Generated by Shorewall $globals{VERSION} - $date\n#\n";
|
|
|
|
print "*filter\n";
|
|
|
|
print ":$_ ACCEPT\n" for qw( INPUT OUTPUT FORWARD );
|
|
|
|
for ( @builtins ) {
|
|
my $rules = $arp_table{$_};
|
|
|
|
while ( my $rule = shift @$rules ) {
|
|
print "-A $rule\n";
|
|
}
|
|
}
|
|
|
|
print "COMMIT\n" if $arptablesjf;
|
|
|
|
print "\n";
|
|
}
|
|
|
|
1;
|