# # 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 . # # 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 $VERSION = 'MODULEVERSION'; 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;