From 692e83c1c80106c4ec67e842fcde720c9564fd35 Mon Sep 17 00:00:00 2001 From: teastep Date: Sun, 7 Dec 2008 20:54:15 +0000 Subject: [PATCH] More IPv6 Changes git-svn-id: https://shorewall.svn.sourceforge.net/svnroot/shorewall/trunk@8939 fbd18981-670d-0410-9b5c-8dc0c1a9a2bb --- Shorewall-perl/Shorewall/Chains.pm | 15 +- Shorewall-perl/Shorewall/IPAddrs.pm | 269 +++++++++++++++++++++++--- Shorewall-perl/Shorewall/Providers.pm | 4 +- Shorewall-perl/Shorewall/Rules.pm | 22 +-- Shorewall-perl/Shorewall/Zones.pm | 8 +- 5 files changed, 269 insertions(+), 49 deletions(-) diff --git a/Shorewall-perl/Shorewall/Chains.pm b/Shorewall-perl/Shorewall/Chains.pm index 3fd7151ad..fe9f65240 100644 --- a/Shorewall-perl/Shorewall/Chains.pm +++ b/Shorewall-perl/Shorewall/Chains.pm @@ -70,6 +70,9 @@ our %EXPORT_TAGS = ( OUTPUT_RESTRICT POSTROUTE_RESTRICT ALL_RESTRICT + + use_ipv4_chains + use_ipv6_chains add_command add_commands @@ -261,13 +264,13 @@ use constant { NULL_MODE => 0 , # Generating neither shell commands nor iptabl our $mode; -sub use_ipv4() { +sub use_ipv4_chains() { $nat_table = $chain_table{nat}; $mangle_table = $chain_table{mangle}; $filter_table = $chain_table{filter}; } -sub use_ipv6() { +sub use_ipv6_chains() { $nat_table = undef; $mangle_table = $chain_table{mangle6}; $filter_table = $chain_table{filter6}; @@ -294,11 +297,11 @@ sub initialize() { $chain_table{mangle}{__NAME__} = 'mangle'; $chain_table{nat}{__NAME__} = 'nat'; $chain_table{filter}{__NAME__} = 'filter'; - $chain_table{raw6}{__NAME__} = 'raw'; - $chain_table{mangle6}{__NAME__} = 'mangle'; - $chain_table{filter6}{__NAME__} = 'filter'; + $chain_table{raw6}{__NAME__} = 'raw6'; + $chain_table{mangle6}{__NAME__} = 'mangle6'; + $chain_table{filter6}{__NAME__} = 'filter6'; - use_ipv4; + use_ipv4_chains; # # These get set to 1 as sections are encountered. # diff --git a/Shorewall-perl/Shorewall/IPAddrs.pm b/Shorewall-perl/Shorewall/IPAddrs.pm index 5e72637a6..d859731ac 100644 --- a/Shorewall-perl/Shorewall/IPAddrs.pm +++ b/Shorewall-perl/Shorewall/IPAddrs.pm @@ -26,17 +26,25 @@ # package Shorewall::IPAddrs; require Exporter; +use Socket6; use Shorewall::Config qw( :DEFAULT split_list require_capability in_hex8); use strict; our @ISA = qw(Exporter); our @EXPORT = qw( ALLIPv4 + ALLIPv6 + ALLIP + ALL TCP UDP ICMP + IPv6_ICMP SCTP + F_INET + F_INET6 + validate_address validate_net decompose_net @@ -45,33 +53,82 @@ our @EXPORT = qw( ALLIPv4 ip_range_explicit expand_port_range allipv4 + allipv6 + allip rfc1918_networks resolve_proto proto_name + use_ipv4_addrs + use_ipv6_addrs + using_ipv4_addrs + using_ipv6_addrs validate_port validate_portpair validate_port_list validate_icmp ); our @EXPORT_OK = qw( ); -our $VERSION = 4.1.5; +our $VERSION = 4.3.0; # -# Some IPv4 useful stuff +# Some IPv4/6 useful stuff # our @allipv4 = ( '0.0.0.0/0' ); +our @allipv6 = ( '::/0' ); +our $family; -use constant { ALLIPv4 => '0.0.0.0/0' , ICMP => 1, TCP => 6, UDP => 17 , SCTP => 132 }; +use constant { ALLIPv4 => '0.0.0.0/0' , + ALLIPv6 => '::/0' , + F_INET => 1, + F_INET6 => 2, + ICMP => 1, + TCP => 6, + UDP => 17, + ICMPv6_ICMP => 58, + SCTP => 132 }; our @rfc1918_networks = ( "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ); +sub use_ipv4() { + $family = F_INET; +} + +sub using_ipv4() { + $family == F_INET; +} + +sub use_ipv6() { + $family = F_INET6; +} + +sub using_ipv6() { + $family == F_INET6; +} + +# +# 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() { + use_ipv4; +} + +INIT { + initialize; +} + sub vlsm_to_mask( $ ) { my $vlsm = $_[0]; in_hex8 ( ( 0xFFFFFFFF << ( 32 - $vlsm ) ) && 0xFFFFFFFF ); } -sub valid_address( $ ) { +sub valid_4address( $ ) { my $address = $_[0]; my @address = split /\./, $address; @@ -83,20 +140,19 @@ sub valid_address( $ ) { 1; } -sub validate_address( $$ ) { +sub validate_4address( $$ ) { my ( $addr, $allow_name ) = @_; my @addrs = ( $addr ); - unless ( valid_address $addr ) { + unless ( valid_4address $addr ) { fatal_error "Invalid IP Address ($addr)" unless $allow_name; fatal_error "Unknown Host ($addr)" unless (@addrs = gethostbyname $addr); if ( defined wantarray ) { shift @addrs for (1..4); for ( @addrs ) { - my (@a) = unpack('C4',$_); - $_ = join('.', @a ); + $_ = inet_htoa $_; } } } @@ -130,7 +186,7 @@ sub encodeaddr( $ ) { $result; } -sub validate_net( $$ ) { +sub validate_4net( $$ ) { my ($net, $vlsm, $rest) = split( '/', $_[0], 3 ); my $allow_name = $_[1]; @@ -142,10 +198,10 @@ sub validate_net( $$ ) { if ( defined $vlsm ) { fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= 32; fatal_error "Invalid Network address ($_[0])" if defined $rest; - fatal_error "Invalid IP address ($net)" unless valid_address $net; + fatal_error "Invalid IP address ($net)" unless valid_4address $net; } else { fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/' || ! defined $net; - validate_address $net, $_[1]; + validate_4address $net, $_[1]; $vlsm = 32; } @@ -159,11 +215,11 @@ sub validate_net( $$ ) { } } -sub validate_range( $$ ) { +sub validate_4range( $$ ) { my ( $low, $high ) = @_; - validate_address $low, 0; - validate_address $high, 0; + validate_4address $low, 0; + validate_4address $high, 0; my $first = decodeaddr $low; my $last = decodeaddr $high; @@ -171,6 +227,16 @@ sub validate_range( $$ ) { fatal_error "Invalid IP Range ($low-$high)" unless $first <= $last; } +sub validate_4host( $$ ) { + my ( $host, $allow_name ) = $_[0]; + + if ( $host =~ /^(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) { + validate_4ange $1, $2; + } else { + validate_4net( $host, $allow_name ); + } +} + sub ip_range_explicit( $ ) { my $range = $_[0]; my @result; @@ -182,7 +248,7 @@ sub ip_range_explicit( $ ) { push @result, $low; if ( defined $high ) { - validate_address $high, 0; + validate_faddress $high, 0; my $first = decodeaddr $low; my $last = decodeaddr $high; @@ -209,20 +275,14 @@ sub decompose_net( $ ) { } -sub validate_host( $$ ) { - my ( $host, $allow_name ) = $_[0]; - - if ( $host =~ /^(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) { - validate_range $1, $2; - } else { - validate_net( $host, $allow_name ); - } -} - sub allipv4() { @allipv4; } +sub allipv6() { + @allipv6; +} + sub rfc1918_networks() { @rfc1918_networks } @@ -231,7 +291,7 @@ sub rfc1918_networks() { # Protocol/port validation # -our %nametoproto = ( all => 0, ALL => 0, icmp => 1, ICMP => 1, tcp => 6, TCP => 6, udp => 17, UDP => 17 ); +our %nametoproto = ( all => 0, ALL => 0, icmp => 1, ICMP => 1, tcp => 6, TCP => 6, udp => 17, UDP => 17 ); our @prototoname = ( 'all', 'icmp', '', '', '', '', 'tcp', '', '', '', '', '', '', '', '', '', '', 'udp' ); # @@ -345,6 +405,8 @@ my %icmp_types = ( any => 'any', 'address-mask-reply' => 18 ); sub validate_icmp( $ ) { + fatal_error "IPv4 ICMP not allowed in an IPv6 Rule" unless $family == F_INET; + my $type = $_[0]; my $value = $icmp_types{$type}; @@ -419,4 +481,159 @@ sub expand_port_range( $$ ) { } } +sub valid_6address( $ ) { + my $address = $_[0]; + + my @address = split /:/, $address; + + return 0 if @address > 8; + return 0 if @address < 8 && ! $address =~ /::/; + return 0 if $address =~ /:::/ || $address =~ /::.*::/; + + if ( $address =~ /^:/ ) { + unless ( $address eq '::' ) { + return 0 if $address =~ /:$/ || $address =~ /^:.*::/; + } + } elsif ( $address =~ /:$/ ) { + return 0 if $address =~ /::.*:$/; + } + + for my $a ( @address ) { + return 0 unless $a eq '' || ( $a =~ /^[a-fA-f\d]+$/ && oct "0x$a" < 65536 ); + } + + 1; +} + +sub validate_6address( $$ ) { + my ( $addr, $allow_name ) = @_; + + my @addrs = ( $addr ); + + unless ( valid_6address $addr ) { + fatal_error "Invalid IPv6 Address ($addr)" unless $allow_name; + fatal_error "Unknown Host ($addr)" unless (@addrs = gethostbyname2 $addr, AF_INET6); + + if ( defined wantarray ) { + shift @addrs for (1..4); + for ( @addrs ) { + $_ = inet_ntop AF_INET6, $_; + } + } + } + + defined wantarray ? wantarray ? @addrs : $addrs[0] : undef; +} + +sub validate_6net( $$ ) { + my ($net, $vlsm, $rest) = split( '/', $_[0], 3 ); + my $allow_name = $_[1]; + + fatal_error "An ipset name ($net) is not allowed in this context" if substr( $net, 0, 1 ) eq '+'; + + if ( defined $vlsm ) { + fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= 64; + fatal_error "Invalid Network address ($_[0])" if defined $rest; + fatal_error "Invalid IPv6 address ($net)" unless valid_6address $net; + } else { + fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/' || ! defined $net; + validate_6address $net, $allow_name; + } +} + +sub validate_6range( $$ ) { + my ( $low, $high ) = @_; + + validate_6address $low, 0; + validate_6address $high, 0; + + my @low = split ":", $low; + my @high = split ":", $high; + + if ( @low == @high ) { + my ( $l, $h) = ( pop @low, pop @high ); + + return 1 if hex "0x$l" <= hex "0x$h" && join( ":", @low ) eq join( ":", @high ); + } + + fatal_error "Invalid IPv6 Range ($low-$high)"; +} + +sub validate_6host( $$ ) { + my ( $host, $allow_name ) = $_[0]; + + if ( $host =~ /^(.*:.*)-(.*:.*)$/ ) { + validate_6range $1, $2; + } else { + validate_6net( $host, $allow_name ); + } +} + +my %ipv6_icmp_types = ( any => 'any', + 'destination-unreachable' => 1, + 'no-route' => '1/0', + 'communication-prohibited' => '1/1', + 'address-unreachable' => '1/2', + 'port-unreachable' => '1/3', + 'packet-too-big' => 2, + 'time-exceeded' => 3, + 'ttl-exceeded' => 3, + 'ttl-zero-during-transit' => '3/0', + 'ttl-zero-during-reassembly' => '3/1', + 'parameter-problem' => 4, + 'bad-header' => '4/0', + 'unknown-header-type' => '4/1', + 'unknown-option' => '4/2', + 'echo-request' => 128, + 'echo-reply' => 129, + 'router-solicitation' => 133, + 'router-advertisement' => 134, + 'neighbour-solicitation' => 135, + 'neighbour-advertisement' => 136, + redirect => 137 ); + + +sub validate_icmp6( $ ) { + fatal_error "IPv6 ICMP not allowed in an IPv4 Rule" unless $family == F_INET6; + my $type = $_[0]; + + my $value = $ipv6_icmp_types{$type}; + + return $value if defined $value; + + if ( $type =~ /^(\d+)(\/(\d+))?$/ ) { + return $type if $1 < 256 && ( ! $2 || $3 < 256 ); + } + + fatal_error "Invalid IPv6 ICMP Type ($type)" +} + +sub ALLIP() { + $family == F_INET ? ALLIPv4 : ALLIPv6; +} + +sub allip() { + $family == F_INET ? ALLIPv4 : ALLIPv6; +} + +sub valid_address ( $ ) { + $family == F_INET ? valid_4address( $_[0] ) : valid_6address( $_[0] ); +} + +sub validate_address ( $$ ) { + $family == F_INET ? validate_4address( $_[0], $_[1] ) : validate_6address( $_[0], $_[1] ); +} + +sub validate_net ( $$ ) { + $family == F_INET ? validate_4net( $_[0], $_[1] ) : validate_6net( $_[0], $_[1] ); +} + +sub validate_range ($$ ) { + $family == F_INET ? validate_4range( $_[0], $_[1] ) : validate_6range( $_[0], $_[1] ); +} + +sub validate_host ($$ ) { + $family == F_INET ? validate_4host( $_[0], $_[1] ) : validate_6host( $_[0], $_[1] ); +} + 1; diff --git a/Shorewall-perl/Shorewall/Providers.pm b/Shorewall-perl/Shorewall/Providers.pm index b81cb6f66..d1a9bd6c9 100644 --- a/Shorewall-perl/Shorewall/Providers.pm +++ b/Shorewall-perl/Shorewall/Providers.pm @@ -419,14 +419,14 @@ sub add_an_rtrule( $$$$ ) { fatal_error "You must specify either the source or destination in a route_rules entry" if $source eq '-' && $dest eq '-'; if ( $dest eq '-' ) { - $dest = 'to ' . ALLIPv4; + $dest = 'to ' . ALLIP; } else { validate_net( $dest, 0 ); $dest = "to $dest"; } if ( $source eq '-' ) { - $source = 'from ' . ALLIPv4; + $source = 'from ' . ALLIP; } elsif ( $source =~ /:/ ) { ( my $interface, $source , my $remainder ) = split( /:/, $source, 3 ); fatal_error "Invalid SOURCE" if defined $remainder; diff --git a/Shorewall-perl/Shorewall/Rules.pm b/Shorewall-perl/Shorewall/Rules.pm index ee4b0a6a9..dc96ea69f 100644 --- a/Shorewall-perl/Shorewall/Rules.pm +++ b/Shorewall-perl/Shorewall/Rules.pm @@ -177,7 +177,7 @@ sub setup_ecn() $interfaces{$interface} = 1; - $hosts = ALLIPv4 if $hosts eq '-'; + $hosts = ALLIP if $hosts eq '-'; for my $host( split_list $hosts, 'address' ) { validate_host( $host , 1 ); @@ -361,7 +361,7 @@ sub process_criticalhosts() { fatal_error "Unknown interface ($interface)" unless known_interface $interface; - $hosts = ALLIPv4 unless $hosts ne '-'; + $hosts = ALLIP unless $hosts ne '-'; my @hosts; @@ -402,7 +402,7 @@ sub process_routestopped() { fatal_error "Unknown interface ($interface)" unless known_interface $interface; - $hosts = ALLIPv4 unless $hosts && $hosts ne '-'; + $hosts = ALLIP unless $hosts && $hosts ne '-'; my @hosts; @@ -1034,7 +1034,7 @@ sub process_rule1 ( $$$$$$$$$$$$$ ) { $source = $2; } else { $sourcezone = $source; - $source = ALLIPv4; + $source = ALLIP; } if ( $dest =~ /^(.*?):(.*)/ ) { @@ -1048,7 +1048,7 @@ sub process_rule1 ( $$$$$$$$$$$$$ ) { $destzone = '-'; } else { $destzone = $dest; - $dest = ALLIPv4; + $dest = ALLIP; } fatal_error "Missing source zone" if $sourcezone eq '-' || $sourcezone =~ /^:/; @@ -1073,7 +1073,7 @@ sub process_rule1 ( $$$$$$$$$$$$$ ) { # # For compatibility with older Shorewall versions # - $origdest = ALLIPv4 if $origdest eq 'all'; + $origdest = ALLIP if $origdest eq 'all'; # # Take care of chain @@ -1178,14 +1178,14 @@ sub process_rule1 ( $$$$$$$$$$$$$ ) { $target = '-j REDIRECT '; $target .= "--to-port $serverport " if $serverport; if ( $origdest eq '' || $origdest eq '-' ) { - $origdest = ALLIPv4; + $origdest = ALLIP; } elsif ( $origdest eq 'detect' ) { if ( $config{DETECT_DNAT_IPADDRS} && $sourcezone ne firewall_zone ) { my $interfacesref = $sourceref->{interfaces}; my @interfaces = keys %$interfacesref; $origdest = @interfaces ? "detect:@interfaces" : ALLIPv4; } else { - $origdest = ALLIPv4; + $origdest = ALLIP; } } } else { @@ -1218,9 +1218,9 @@ sub process_rule1 ( $$$$$$$$$$$$$ ) { if ( $config{DETECT_DNAT_IPADDRS} && $sourcezone ne firewall_zone ) { my $interfacesref = $sourceref->{interfaces}; my @interfaces = keys %$interfacesref; - $origdest = @interfaces ? "detect:@interfaces" : ALLIPv4; + $origdest = @interfaces ? "detect:@interfaces" : ALLIP; } else { - $origdest = ALLIPv4; + $origdest = ALLIP; } } } @@ -1265,7 +1265,7 @@ sub process_rule1 ( $$$$$$$$$$$$$ ) { if ( $origdest eq 'detect' ) { my $interfacesref = $sourceref->{interfaces}; my $interfaces = "@$interfacesref"; - $origdest = $interfaces ? "detect:$interfaces" : ALLIPv4; + $origdest = $interfaces ? "detect:$interfaces" : ALLIP; } expand_rule( ensure_chain ('nat' , $sourceref->{type} eq 'firewall' ? 'OUTPUT' : dnat_chain $sourcezone) , diff --git a/Shorewall-perl/Shorewall/Zones.pm b/Shorewall-perl/Shorewall/Zones.pm index a8aa45ada..0f5adf192 100644 --- a/Shorewall-perl/Shorewall/Zones.pm +++ b/Shorewall-perl/Shorewall/Zones.pm @@ -481,7 +481,7 @@ sub add_group_to_zone($$$$$) 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; + $ifacezone = $zone if $host eq ALLIP; } } @@ -746,7 +746,7 @@ sub validate_interfaces_file( $ ) push @ifaces, $interface; - my @networks = allipv4; + my @networks = allip; add_group_to_zone( $zone, $zoneref->{type}, $interface, \@networks, $optionsref ) if $zone; @@ -993,7 +993,7 @@ sub validate_hosts_file() # # Take care of case where the hosts list begins with '!' # - $hosts = join( '', ALLIPv4 , $hosts ) if substr($hosts, 0, 2 ) eq ',!'; + $hosts = join( '', ALLIP , $hosts ) if substr($hosts, 0, 2 ) eq ',!'; add_group_to_zone( $zone, $type , $interface, [ split_list( $hosts, 'host' ) ] , $optionsref); @@ -1027,7 +1027,7 @@ sub find_hosts_by_option( $ ) { for my $interface ( @interfaces ) { if ( ! $interfaces{$interface}{zone4} && $interfaces{$interface}{options}{$option} ) { - push @hosts, [ $interface, 'none', ALLIPv4 ]; + push @hosts, [ $interface, 'none', ALLIP ]; } }