# # Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Interfaces.pm # # This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] # # (c) 2007 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # This program is free software; you can redistribute it and/or modify # it under the terms of Version 2 of the GNU General Public License # as published by the Free Software Foundation. # # 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, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # # This Module contains the code for processing the /etc/shorewall/interfaces # file. It also exports 'add_group_to_zone()' which other modules call to # alter zone membership. # package Shorewall::Interfaces; require Exporter; use Shorewall::Common; use Shorewall::Config; use Shorewall::IPAddrs; use Shorewall::Zones; use strict; our @ISA = qw(Exporter); our @EXPORT = qw( add_group_to_zone validate_interfaces_file known_interface port_to_bridge source_port_to_bridge interface_is_optional find_interfaces_by_option get_interface_option @interfaces @bridges ); our @EXPORT_OK = qw( initialize ); our @VERSION = 1.00; # # Interface Table. # # @interfaces lists the interface names in the order that they appear in the interfaces file. # # %interfaces { => { root => # options => { = , # ... # } # zone => # bridge => # } # } # our @interfaces; our %interfaces; our @bridges; # # Initialize globals -- we take this novel approach to globals initialization to allow # the compiler to run multiple times in the same process. The # initialize() function does globals initialization for this # module and is called from an INIT block below. The function is # also called by Shorewall::Compiler::compiler at the beginning of # the second and subsequent calls to that function. # sub initialize() { @interfaces = (); %interfaces = (); @bridges = (); } INIT { initialize; } sub add_group_to_zone($$$$$) { my ($zone, $type, $interface, $networks, $options) = @_; my $typeref; my $interfaceref; my $arrayref; my $zoneref = $zones{$zone}; my $zonetype = $zoneref->{type}; my $ifacezone = $interfaces{$interface}{zone}; $zoneref->{interfaces}{$interface} = 1; my @newnetworks; my @exclusions; my $new = \@newnetworks; my $switched = 0; $ifacezone = '' unless defined $ifacezone; for my $host ( @$networks ) { fatal_error "Invalid Host List" unless defined $host and $host ne ''; if ( substr( $host, 0, 1 ) eq '!' ) { fatal_error "Only one exclusion allowed in a host list" if $switched; $switched = 1; $host = substr( $host, 1 ); $new = \@exclusions; } unless ( $switched ) { if ( $type eq $zonetype ) { fatal_error "Duplicate Host Group ($interface:$host) in zone $zone" if $ifacezone eq $zone; $ifacezone = $zone if $host eq ALLIPv4; } } push @$new, $switched ? "$interface:$host" : $host; } $zoneref->{options}{in_out}{routeback} = 1 if $options->{routeback}; $typeref = ( $zoneref->{hosts} || ( $zoneref->{hosts} = {} ) ); $interfaceref = ( $typeref->{$type} || ( $interfaceref = $typeref->{$type} = {} ) ); $arrayref = ( $interfaceref->{$interface} || ( $interfaceref->{$interface} = [] ) ); $zoneref->{options}{complex} = 1 if @$arrayref || ( @newnetworks > 1 ) || ( @exclusions ); my %h; $h{options} = $options; $h{hosts} = \@newnetworks; $h{ipsec} = $type eq 'ipsec' ? 'ipsec' : 'none'; push @{$zoneref->{exclusions}}, @exclusions; push @{$arrayref}, \%h; } # # Return a list of networks routed out of the passed interface # sub get_routed_networks ( $$ ) { my ( $interface , $error_message ) = @_; my @networks; if ( open IP , '-|' , "/sbin/ip route show dev $interface 2> /dev/null" ) { while ( my $route = ) { $route =~ s/^\s+//; my $network = ( split /\s+/, $route )[0]; if ( $network eq 'default' ) { fatal_error $error_message if $error_message; warning_message "default route ignored on interface $interface"; } else { my ( $address, $vlsm ) = split '/', $network; $vlsm = 32 unless defined $vlsm; push @networks, "$address/$vlsm"; } } close IP } @networks; } # # Parse the interfaces file. # sub validate_interfaces_file( $ ) { my $export = shift; use constant { SIMPLE_IF_OPTION => 1, BINARY_IF_OPTION => 2, ENUM_IF_OPTION => 3, MASK_IF_OPTION => 3, IF_OPTION_ZONEONLY => 4 }; my %validoptions = (arp_filter => BINARY_IF_OPTION, arp_ignore => ENUM_IF_OPTION, blacklist => SIMPLE_IF_OPTION, bridge => SIMPLE_IF_OPTION, detectnets => SIMPLE_IF_OPTION, dhcp => SIMPLE_IF_OPTION, maclist => SIMPLE_IF_OPTION, logmartians => BINARY_IF_OPTION, norfc1918 => SIMPLE_IF_OPTION, nosmurfs => SIMPLE_IF_OPTION, optional => SIMPLE_IF_OPTION, proxyarp => BINARY_IF_OPTION, routeback => SIMPLE_IF_OPTION + IF_OPTION_ZONEONLY, routefilter => BINARY_IF_OPTION, sourceroute => BINARY_IF_OPTION, tcpflags => SIMPLE_IF_OPTION, upnp => SIMPLE_IF_OPTION, ); my $fn = open_file 'interfaces'; my $first_entry = 1; my @ifaces; while ( read_a_line ) { if ( $first_entry ) { progress_message2 "$doing $fn..."; $first_entry = 0; } my ($zone, $interface, $networks, $options ) = split_line 2, 4, 'interfaces file'; my $zoneref; my $bridge = ''; if ( $zone eq '-' ) { $zone = ''; } else { $zoneref = $zones{$zone}; fatal_error "Unknown zone ($zone)" unless $zoneref; fatal_error "Firewall zone not allowed in ZONE column of interface record" if $zoneref->{type} eq 'firewall'; } $networks = '' if $networks eq '-'; $options = '' if $options eq '-'; ( $interface, my ($port, $extra) ) = split /:/ , $interface, 3; fatal_error "Invalid INTERFACE" if defined $extra || ! $interface; fatal_error "Invalid Interface Name ($interface)" if $interface eq '+'; if ( defined $port ) { require_capability( 'PHYSDEV_MATCH', 'Bridge Ports', ''); require_capability( 'KLUDGEFREE', 'Bridge Ports', ''); fatal_error "Duplicate Interface ($port)" if $interfaces{$port}; fatal_error "$interface is not a defined bridge" unless $interfaces{$interface} && $interfaces{$interface}{options}{bridge}; fatal_error "Invalid Interface Name ($interface:$port)" unless $port =~ /^[\w.@%-]+\+?$/; fatal_error "Bridge Ports may only be associated with 'bport' zones" if $zone && $zoneref->{type} ne 'bport4'; if ( $zone ) { if ( $zoneref->{bridge} ) { fatal_error "Bridge Port zones may only be associated with a single bridge" if $zoneref->{bridge} ne $interface; } else { $zoneref->{bridge} = $interface; } } $interfaces{$port}{bridge} = $bridge = $interface; $interface = $port; } else { fatal_error "Duplicate Interface ($interface)" if $interfaces{$interface}; fatal_error "Zones of type 'bport' may only be associated with bridge ports" if $zone && $zoneref->{type} eq 'bport4'; $interfaces{$interface}{bridge} = $interface; } my $wildcard = 0; if ( $interface =~ /\+$/ ) { $wildcard = 1; $interfaces{$interface}{root} = substr( $interface, 0, -1 ); } else { $interfaces{$interface}{root} = $interface; } unless ( $networks eq '' || $networks eq 'detect' ) { for my $address ( split /,/, $networks ) { fatal_error 'Invalid BROADCAST address' unless $address =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; } warning_message 'Shorewall no longer uses broadcast addresses in rule generation'; } my $optionsref = {}; my %options; if ( $options ) { fatal_error "Bridge Ports may not have options" if defined $port; for my $option (split ',', $options ) { next if $option eq '-'; ( $option, my $value ) = split /=/, $option; fatal_error "Invalid Interface option ($option)" unless my $type = $validoptions{$option}; fatal_error "The \"$option\" option may not be specified on a multi-zone interface" if $type & IF_OPTION_ZONEONLY && ! $zone; $type &= MASK_IF_OPTION; if ( $type == SIMPLE_IF_OPTION ) { fatal_error "Option $option does not take a value" if defined $value; $options{$option} = 1; } elsif ( $type == BINARY_IF_OPTION ) { $value = 1 unless defined $value; fatal_error "Option value for $option must be 0 or 1" unless ( $value eq '0' || $value eq '1' ); fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard; $options{$option} = $value; } elsif ( $type == ENUM_IF_OPTION ) { fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard; if ( $option eq 'arp_ignore' ) { if ( defined $value ) { if ( $value =~ /^[1-3,8]$/ ) { $options{arp_ignore} = $value; } else { fatal_error "Invalid value ($value) for arp_ignore"; } } else { $options{arp_ignore} = 1; } } else { fatal_error "Internal Error in validate_interfaces_file"; } } } $zoneref->{options}{in_out}{routeback} = 1 if $zoneref && $options{routeback}; if ( $options{bridge} ) { require_capability( 'PHYSDEV_MATCH', 'The "bridge" option', 's'); fatal_error "Bridges may not have wildcard names" if $wildcard; push @bridges, $interface; } } elsif ( defined $port ) { $options{port} = 1; } $interfaces{$interface}{options} = $optionsref = \%options; push @ifaces, $interface; my @networks; if ( $options{detectnets} ) { fatal_error "The 'detectnets' option is not allowed on a multi-zone interface" unless $zone; fatal_error "The 'detectnets' option may not be used with a wild-card interface name" if $wildcard; fatal_error "The 'detectnets' option may not be used with the '-e' compiler option" if $export; @networks = get_routed_networks( $interface, 'detectnets not allowed on interface with default route' ); fatal_error "No routes found through 'detectnets' interface $interface" unless @networks || $options{optional}; delete $options{maclist} unless @networks; } else { @networks = @allipv4; } add_group_to_zone( $zone, $zoneref->{type}, $interface, \@networks, $optionsref ) if $zone && @networks; $interfaces{$interface}{zone} = $zone; #Must follow the call to add_group_to_zone() progress_message " Interface \"$line\" Validated"; } # # We now assemble the @interfaces array such that bridge ports immediately precede their associated bridge # for my $interface ( @ifaces ) { my $interfaceref = $interfaces{$interface}; if ( $interfaceref->{options}{bridge} ) { my @ports = grep $interfaces{$_}{options}{port} && $interfaces{$_}{bridge} eq $interface, @ifaces; if ( @ports ) { push @interfaces, @ports; } else { $interfaceref->{options}{routeback} = 1; #so the bridge will work properly } } push @interfaces, $interface unless $interfaceref->{options}{port}; } } # # Returns true if passed interface matches an entry in /etc/shorewall/interfaces # # If the passed name matches a wildcard, a entry for the name is added in %interfaces to speed up validation of other references to that name. # sub known_interface($) { my $interface = $_[0]; return 1 if $interfaces{$interface}; for my $i ( @interfaces ) { my $interfaceref = $interfaces{$i}; my $val = $interfaceref->{root}; next if $val eq $i; if ( substr( $interface, 0, length $val ) eq $val ) { # # Cache this result for future reference # $interfaces{$interface} = { options => $interfaceref->{options}, bridge => $interfaceref->{bridge} }; return 1; } } 0; } # # Return the bridge associated with the passed interface. If the interface is not a bridge port, # return '' # sub port_to_bridge( $ ) { my $portref = $interfaces{$_[0]}; return $portref && $portref->{options}{port} ? $portref->{bridge} : ''; } # # Return the bridge associated with the passed interface. # sub source_port_to_bridge( $ ) { my $portref = $interfaces{$_[0]}; return $portref ? $portref->{bridge} : ''; } # # Return the 'optional' setting of the passed interface # sub interface_is_optional($) { my $optionsref = $interfaces{$_[0]}{options}; $optionsref && $optionsref->{optional}; } # # Returns reference to array of interfaces with the passed option # sub find_interfaces_by_option( $ ) { my $option = $_[0]; my @ints = (); for my $interface ( @interfaces ) { my $optionsref = $interfaces{$interface}{options}; if ( $optionsref && defined $optionsref->{$option} ) { push @ints , $interface } } \@ints; } # # Return the value of an option for an interface # sub get_interface_option( $$ ) { my ( $interface, $option ) = @_; $interfaces{$interface}{options}{$option}; } 1;