From 4c2e2d79f28734c97a0e374ca217e24ff954fe96 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Thu, 16 Feb 2012 15:03:55 -0800 Subject: Adding rule and address setup/parsing libraries (cherry picked from commit 2f59f3ef67d70981ff0501868d5ea206eb9359ae) --- lib/Vyatta/Conntrack/AddressFilterCT.pm | 204 +++++++++++++++ lib/Vyatta/Conntrack/RuleCT.pm | 431 ++++++++++++++++++++++++++++++++ 2 files changed, 635 insertions(+) create mode 100644 lib/Vyatta/Conntrack/AddressFilterCT.pm create mode 100644 lib/Vyatta/Conntrack/RuleCT.pm (limited to 'lib') diff --git a/lib/Vyatta/Conntrack/AddressFilterCT.pm b/lib/Vyatta/Conntrack/AddressFilterCT.pm new file mode 100644 index 0000000..c7dac8d --- /dev/null +++ b/lib/Vyatta/Conntrack/AddressFilterCT.pm @@ -0,0 +1,204 @@ +# Author: Vyatta +# Date: 2007 +# Description: IP tables address filter +# +# Gaurav Sinha: Re-using AddressFilter.pm from vyatta-cfg-firewall package. +# + +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 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. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006-2009 Vyatta, Inc. +# All Rights Reserved. +# **** End License **** + +package Vyatta::Conntrack::AddressFilterCT; + +require Vyatta::Config; +require Vyatta::IpTables::IpSet; +use Vyatta::Misc qw(getPortRuleString); +use Vyatta::TypeChecker; + +use strict; +use warnings; + +my %_protocolswithports = ( + tcp_udp => 1, + # 'tcp_udp' is to be allowed for nat and firewall rules only. + # features should have syntax checks for allowing or forbiding + # the use of 'tcp_udp' as protocol. to allow tcp_udp see syntax check + # in protocol/node.def for NAT rules and to forbid tcp_udp see syntax + # check in protocol/node.def for load-balancing rules + # when allowed : tcp_udp creates 2 iptable rules - one for tcp, other for udp + tcp => 1, + udp => 1, + 6 => 1, + 17 => 1, +); + +my %fields = ( + _srcdst => undef, + _range_start => undef, + _range_stop => undef, + _network => undef, + _address => undef, + _port => undef, + _protocol => undef, + _port_group => undef, +); + +sub new { + my $that = shift; + my $class = ref ($that) || $that; + my $self = { + %fields, + }; + + bless $self, $class; + return $self; +} + +sub setup_base { + my ($self, $level, $func) = @_; + my $config = new Vyatta::Config; + + $config->setLevel("$level"); + + # setup needed parent nodes + $self->{_srcdst} = $config->returnParent(".."); +# $self->{_protocol} = $config->$func(".. protocol"); + + # setup address filter nodes + $self->{_address} = $config->$func("address"); + $self->{_network} = undef; + $self->{_range_start} = undef; + $self->{_range_stop} = undef; + if (defined($self->{_address})) { + if ($self->{_address} =~ /\//) { + $self->{_network} = $self->{_address}; + $self->{_address} = undef; + } elsif ($self->{_address} =~ /^([^-]+)-([^-]+)$/) { + $self->{_range_start} = $1; + $self->{_range_stop} = $2; + $self->{_address} = undef; + } + } + $self->{_port} = $config->$func("port"); + + return 0; +} + +sub setup { + my ($self, $level) = @_; + + $self->setup_base($level, 'returnValue'); + return 0; +} + +sub setupOrig { + my ($self, $level) = @_; + + $self->setup_base($level, 'returnOrigValue'); + return 0; +} + +sub print { + my ($self) = @_; + + print "srcdst: $self->{_srcdst}\n" if defined $self->{_srcdst}; + print "range start: $self->{_range_start}\n" if defined $self->{_range_start}; + print "range stop: $self->{_range_stop}\n" if defined $self->{_range_stop}; + print "network: $self->{_network}\n" if defined $self->{_network}; + print "address: $self->{_address}\n" if defined $self->{_address}; + print "port: $self->{_port}\n" if defined $self->{_port}; + print "protocol: $self->{_protocol}\n" if defined $self->{_protocol}; + + return 0; +} + +sub rule { + my ($self) = @_; + my $rule = ""; + my $can_use_port = 1; + + my $addr_checker; + my $prefix_checker; + my $pure_addr_checker; + my $ip_term; + my $prefix_term; + + $addr_checker = 'ipv4_negate'; + $prefix_checker = 'ipv4net_negate'; + $pure_addr_checker = 'ipv4'; + $ip_term = "IPv4"; + $prefix_term = "subnet"; + + if (!defined($self->{_protocol}) + || !defined($_protocolswithports{$self->{_protocol}})) { + $can_use_port = 0; + } + + # set the address filter parameters + if (defined($self->{_network})) { + my $str = $self->{_network}; + return (undef, "\"$str\" is not a valid $ip_term $prefix_term") + if (!Vyatta::TypeChecker::validateType($prefix_checker, $str, 1)); + my $negate = ''; + if ($str =~ /^\!(.*)$/) { + $str = $1; + $negate = '! '; + } + $rule .= "$negate --$self->{_srcdst} $str "; + } elsif (defined($self->{_address})) { + my $str = $self->{_address}; + return (undef, "\"$str\" is not a valid $ip_term address") + if (!Vyatta::TypeChecker::validateType($addr_checker, $str, 1)); + my $negate = ''; + if ($str =~ /^\!(.*)$/) { + $str = $1; + $negate = '! '; + } + $rule .= "$negate --$self->{_srcdst} $str "; + } elsif ((defined $self->{_range_start}) && (defined $self->{_range_stop})) { + my $start = $self->{_range_start}; + my $stop = $self->{_range_stop}; + return (undef, "\"$start-$stop\" is not a valid IP range") + if (!Vyatta::TypeChecker::validateType($addr_checker, $start, 1) + || !Vyatta::TypeChecker::validateType($pure_addr_checker, $stop, 1)); + my $negate = ''; + if ($self->{_range_start} =~ /^!(.*)$/) { + $start = $1; + $negate = '! '; + } + if ("$self->{_srcdst}" eq "source") { + $rule .= ("-m iprange $negate --src-range $start-$self->{_range_stop} "); + } + elsif ("$self->{_srcdst}" eq "destination") { + $rule .= ("-m iprange $negate --dst-range $start-$self->{_range_stop} "); + } + } + + my ($port_str, $port_err) + = getPortRuleString($self->{_port}, $can_use_port, + ($self->{_srcdst} eq "source") ? "s" : "d", + $self->{_protocol}); + return (undef, $port_err) if (!defined($port_str)); + $rule .= $port_str; + + return ($rule, undef); +} +1; + +# Local Variables: +# mode: perl +# indent-tabs-mode: nil +# perl-indent-level: 2 +# End: diff --git a/lib/Vyatta/Conntrack/RuleCT.pm b/lib/Vyatta/Conntrack/RuleCT.pm new file mode 100644 index 0000000..9329603 --- /dev/null +++ b/lib/Vyatta/Conntrack/RuleCT.pm @@ -0,0 +1,431 @@ +package Vyatta::Conntrack::RuleCT; + +use strict; +use Vyatta::Config; +require Vyatta::Conntrack::AddressFilterCT; + +my $src = new Vyatta::Conntrack::AddressFilterCT; +my $dst = new Vyatta::Conntrack::AddressFilterCT; + +my %fields = ( + _rule_number => undef, + _protocol => undef, + _state => { + _established => undef, + _new => undef, + _related => undef, + _invalid => undef, + }, +); + +my %dummy_rule = ( + _rule_number => 10000, + _protocol => "all", + _state => { + _established => undef, + _new => undef, + _related => undef, + _invalid => undef, + }, +); + +my $DEBUG = 'false'; + +sub new { + my $that = shift; + my $class = ref ($that) || $that; + my $self = { + %fields, + }; + + bless $self, $class; + return $self; +} + +sub setupDummy { + my ($self, $level) = @_; + + %{$self} = %dummy_rule; + $src = new Vyatta::Conntrack::AddressFilterCT; + $dst = new Vyatta::Conntrack::AddressFilterCT; + + # set the default policy + my $config = new Vyatta::Config; + $config->setLevel("$level"); +} + +sub setup_base { + my ($self, $level, $val_func, $exists_func, $addr_setup) = @_; + my $config = new Vyatta::Config; + + $config->setLevel("$level"); + + $self->{_rule_number} = $config->returnParent(".."); + $self->{_protocol} = $config->$val_func("protocol"); + $self->{_state}->{_established} = $config->$val_func("state established"); + $self->{_state}->{_new} = $config->$val_func("state new"); + $self->{_state}->{_related} = $config->$val_func("state related"); + $self->{_state}->{_invalid} = $config->$val_func("state invalid"); + + $src->$addr_setup("$level source"); + $dst->$addr_setup("$level destination"); + + return 0; +} + +sub setup { + my ($self, $level) = @_; + + $self->setup_base($level, 'returnValue', 'exists', 'setup'); + return 0; +} + +sub setupOrig { + my ($self, $level) = @_; + $self->setup_base($level, 'returnOrigValue', 'existsOrig', 'setupOrig'); + return 0; +} + +sub print { + my ( $self ) = @_; + + print "rulenum: $self->{_rule_number}\n" if defined $self->{_rule_number}; + print "protocol: $self->{_protocol}\n" if defined $self->{_protocol}; + print "state: $self->{_state}\n" if defined $self->{_state}; + $src->print(); + $dst->print(); + +} + +sub rule { + my ( $self ) = @_; + my ($rule, $srcrule, $dstrule, $err_str); + my $tcp_and_udp = 0; + + # set CLI rule num as comment + my @level_nodes = split (' ', $self->{_comment}); + $rule .= "-m comment --comment \"$level_nodes[2]-$level_nodes[4]\" "; + + # set the protocol + if (defined($self->{_protocol})) { + my $str = $self->{_protocol}; + my $negate = ''; + if ($str =~ /^\!(.*)$/) { + $str = $1; + $negate = '! '; + } + if ($str eq 'tcp_udp') { + $tcp_and_udp = 1; + $rule .= " $negate -p tcp "; # we'll add the '-p udp' to 2nd rule later + } else { + $rule .= " $negate -p $str "; + } + } + + my $state_str = uc (get_state_str($self)); + if ($state_str ne "") { + $rule .= "-m state --state $state_str "; + } + + # set tcp flags if applicable + my $tcp_flags = undef; + if (defined $self->{_tcp_flags}) { + if (($self->{_protocol} eq "tcp") || ($self->{_protocol} eq "6")) { + $tcp_flags = get_tcp_flags_string($self->{_tcp_flags}); + } else { + return ("TCP flags can only be set if protocol is set to TCP", ); + } + } + if (defined($tcp_flags)) { + $rule .= " -m tcp --tcp-flags $tcp_flags "; + } + + # set the icmp code and type if applicable + if (($self->{_protocol} eq "icmp") || ($self->{_protocol} eq "1")) { + if (defined $self->{_icmp_name}) { + if (defined($self->{_icmp_type}) || defined($self->{_icmp_code})){ + return ("Cannot use ICMP type/code with ICMP type-name", ); + } + $rule .= "--icmp-type $self->{_icmp_name} "; + } elsif (defined $self->{_icmp_type}) { + $rule .= "--icmp-type $self->{_icmp_type}"; + if (defined $self->{_icmp_code}) { + $rule .= "/$self->{_icmp_code}"; + } + $rule .= " "; + } elsif (defined $self->{_icmp_code}) { + return ("ICMP code can only be defined if ICMP type is defined", ); + } + } elsif (defined($self->{_icmp_type}) || defined($self->{_icmp_code}) + || defined($self->{_icmp_name})) { + return ("ICMP type/code or type-name can only be defined if protocol is ICMP", ); + } + + # Setup ICMPv6 rule if configured + # ICMPv6 parameters are only valid if the rule is matching on the + # ICMPv6 protocol ID. + # + if (($self->{_protocol} eq "icmpv6") || + ($self->{_protocol} eq "ipv6-icmp") || + ($self->{_protocol} eq "58")) { + if (defined($self->{_icmpv6_type})) { + $rule .= "-m icmpv6 --icmpv6-type $self->{_icmpv6_type}"; + } + } + + # add the source and destination rules + ($srcrule, $err_str) = $src->rule(); + return ($err_str, ) if (!defined($srcrule)); + ($dstrule, $err_str) = $dst->rule(); + return ($err_str, ) if (!defined($dstrule)); + if ((grep /multiport/, $srcrule) ^ (grep /multiport/, $dstrule)) { + if ((grep /sport/, $srcrule) && (grep /dport/, $dstrule)) { + return ('Cannot specify multiple ports when both ' + . 'source and destination ports are specified', ); + } + } + $rule .= " $srcrule $dstrule "; + + return ('Cannot specify both "match-frag" and "match-non-frag"', ) + if (defined($self->{_frag}) && defined($self->{_non_frag})); + if (defined($self->{_frag})) { + $rule .= ' -f '; + } elsif (defined($self->{_non_frag})) { + $rule .= ' ! -f '; + } + + # note: "out" is not valid in the INPUT chain. + return ('Cannot specify both "match-ipsec" and "match-none"', ) + if (defined($self->{_ipsec}) && defined($self->{_non_ipsec})); + if (defined($self->{_ipsec})) { + $rule .= ' -m policy --pol ipsec --dir in '; + } elsif (defined($self->{_non_ipsec})) { + $rule .= ' -m policy --pol none --dir in '; + } + + my $p2p = undef; + if (defined($self->{_p2p}->{_all})) { + $p2p = '--apple --bit --dc --edk --gnu --kazaa '; + } else { + my @apps = qw(apple bit dc edk gnu kazaa); + foreach (@apps) { + if (defined($self->{_p2p}->{"_$_"})) { + $p2p .= "--$_ "; + } + } + } + if (defined($p2p)) { + $rule .= " -m ipp2p $p2p "; + } + + my $time = undef; + if (defined($self->{_time}->{_utc})) { + $time .= " --utc "; + } + if (defined($self->{_time}->{_startdate})) { + my $check_date = validate_date($self->{_time}->{_startdate}, "startdate"); + if (!($check_date eq "")) { + return ($check_date, ); + } + $time .= " --datestart $self->{_time}->{_startdate} "; + } + if (defined($self->{_time}->{_stopdate})) { + my $check_date = validate_date($self->{_time}->{_stopdate}, "stopdate"); + if (!($check_date eq "")) { + return ($check_date, ); + } + $time .= " --datestop $self->{_time}->{_stopdate} "; + } + if (defined($self->{_time}->{_starttime})) { + return ("Invalid starttime $self->{_time}->{_starttime}. +Time should use 24 hour notation hh:mm:ss and lie in between 00:00:00 and 23:59:59", ) + if (!validate_timevalues($self->{_time}->{_starttime}, "time")); + $time .= " --timestart $self->{_time}->{_starttime} "; + } + if (defined($self->{_time}->{_stoptime})) { + return ("Invalid stoptime $self->{_time}->{_stoptime}. +Time should use 24 hour notation hh:mm:ss and lie in between 00:00:00 and 23:59:59", ) + if (!validate_timevalues($self->{_time}->{_stoptime}, "time")); + $time .= " --timestop $self->{_time}->{_stoptime} "; + } + if (defined($self->{_time}->{_monthdays})) { + my $negate = " "; + if ($self->{_time}->{_monthdays} =~ m/^!/) { + $negate = "! "; + $self->{_time}->{_monthdays} = substr $self->{_time}->{_monthdays}, 1; + } + return ("Invalid monthdays value $self->{_time}->{_monthdays}. +Monthdays should have values between 1 and 31 with multiple days separated by commas +eg. 2,12,21 For negation, add ! in front eg. !2,12,21", ) + if (!validate_timevalues($self->{_time}->{_monthdays}, "monthdays")); + $time .= " $negate --monthdays $self->{_time}->{_monthdays} "; + } + if (defined($self->{_time}->{_weekdays})) { + my $negate = " "; + if ($self->{_time}->{_weekdays} =~ m/^!/) { + $negate = "! "; + $self->{_time}->{_weekdays} = substr $self->{_time}->{_weekdays}, 1; + } + return ("Invalid weekdays value $self->{_time}->{_weekdays}. +Weekdays should be specified using the first three characters of the day with the +first character capitalized eg. Mon,Thu,Sat For negation, add ! in front eg. !Mon,Thu,Sat", ) + if (!validate_timevalues($self->{_time}->{_weekdays}, "weekdays")); + $time .= " $negate --weekdays $self->{_time}->{_weekdays} "; + } + if (defined($time)) { + $rule .= " -m time $time "; + } + + my $limit = undef; + if (defined $self->{_limit}->{_rate}) { + my $rate_integer = $self->{_limit}->{_rate}; + $rate_integer =~ s/\/(second|minute|hour|day)//; + if ($rate_integer < 1) { + return ("integer value in rate cannot be less than 1", ); + } + $limit = "--limit $self->{_limit}->{_rate} --limit-burst $self->{_limit}->{_burst}"; + } + $rule .= " -m limit $limit " if defined $limit; + + # recent match condition SHOULD BE DONE IN THE LAST so + # all options in $rule are copied to $recent_rule below + my $recent_rule = undef; + if (defined($self->{_recent_time}) || defined($self->{_recent_cnt})) { + my $recent_rule1 = undef; + my $recent_rule2 = undef; + $recent_rule1 .= ' -m recent --update '; + $recent_rule2 .= ' -m recent --set '; + if (defined($self->{_recent_time})) { + $recent_rule1 .= " --seconds $self->{_recent_time} "; + } + if (defined($self->{_recent_cnt})) { + $recent_rule1 .= " --hitcount $self->{_recent_cnt} "; + } + + $recent_rule = $rule; + + if ($rule =~ m/\-m\s+set\s+\-\-match\-set/) { + # firewall group being used in this rule. iptables complains if recent + # match condition is placed after group match conditions [see bug 5744] + # so instead of appending recent match place it before group match + my @split_rules = (); + + @split_rules = split(/(\-m\s+set\s+\-\-match\-set)/, $rule, 2); + $rule = $split_rules[0] . $recent_rule1 . + $split_rules[1] . $split_rules[2]; + + @split_rules = split(/(\-m\s+set\s+\-\-match\-set)/, $recent_rule, 2); + $recent_rule = $split_rules[0] . $recent_rule2 . + $split_rules[1] . $split_rules[2]; + } else { + # append recent match conditions to the two rules needed for recent match + $rule .= $recent_rule1; + $recent_rule .= $recent_rule2; + } + } + + my $chain = $self->{_name}; + my $rule_num = $self->{_rule_number}; + my $rule2 = undef; + # set the jump target. Depends on action and log + if ("$self->{_log}" eq "enable") { + $rule2 = $rule; + my $log_prefix = get_log_prefix($chain, $rule_num, $self->{_action}); + $rule2 .= "-j LOG --log-prefix \"$log_prefix\" "; + } + if ("$self->{_action}" eq "drop") { + $rule .= "-j DROP "; + } elsif ("$self->{_action}" eq "accept") { + $rule .= "-j RETURN "; + } elsif ("$self->{_action}" eq "reject") { + $rule .= "-j REJECT "; + } elsif ("$self->{_action}" eq 'inspect') { + my $target = ipt_get_queue_target('SNORT'); + return ('Undefined target for inspect', ) if ! defined $target; + $rule .= "-j $target "; + } elsif ("$self->{_action}" eq 'modify') { + # mangle actions + my $count = 0; + if (defined($self->{_mod_mark})) { + # MARK + $rule .= "-j MARK --set-mark $self->{_mod_mark} "; + $count++; + } + if (defined($self->{_mod_dscp})) { + # DSCP + $rule .= "-j DSCP --set-dscp $self->{_mod_dscp} "; + $count++; + } + if (defined($self->{_mod_tcpmss})) { + # TCP-MSS + # check for SYN flag + if (!defined $self->{_tcp_flags} || + !(($self->{_tcp_flags} =~ m/SYN/) && !($self->{_tcp_flags} =~ m/!SYN/))) { + return ('need to set TCP SYN flag to modify TCP MSS', ); + } + + if ($self->{_mod_tcpmss} =~ m/\d/) { + $rule .= "-j TCPMSS --set-mss $self->{_mod_tcpmss} "; + } else { + $rule .= "-j TCPMSS --clamp-mss-to-pmtu "; + } + $count++; + } + + # others + + if ($count == 0) { + return ('Action "modify" requires more specific configuration under ' + . 'the "modify" node', ); + } elsif ($count > 1) { + return ('Cannot define more than one modification under ' + . 'the "modify" node', ); + } + } else { + return ("\"action\" must be defined", ); + } + if (defined($rule2)) { + my $tmp = $rule2; + $rule2 = $rule; + $rule = $tmp; + } elsif (defined($recent_rule)) { + $rule2 = $recent_rule; + $recent_rule = undef; + } + + return (undef, undef) if defined $self->{_disable}; + + my ($udp_rule, $udp_rule2, $udp_recent_rule) = (undef, undef, undef); + if ($tcp_and_udp == 1) { + # create udp rules + $udp_rule = $rule; + $udp_rule2 = $rule2 if defined $rule2; + $udp_recent_rule = $recent_rule if defined $recent_rule; + foreach my $each_udprule ($udp_rule, $udp_rule2, $udp_recent_rule) { + $each_udprule =~ s/ \-p tcp / -p udp / if defined $each_udprule; + } + } + + if ($DEBUG eq 'true') { + # print all potential iptables rules that could be formed for + # a single CLI rule. see get_num_ipt_rules to see exact count + print "rule :\n$rule\n" if defined $rule; + print "rule2 :\n$rule2\n" if defined $rule2; + print "recent rule :\n$recent_rule\n" if defined $recent_rule; + print "udp rule :\n$udp_rule\n" if defined $udp_rule; + print "udp rule2 :\n$udp_rule2\n" if defined $udp_rule2; + print "udp recent rule :\n$udp_recent_rule\n" if defined $udp_recent_rule; + } + + return (undef, $rule, $rule2, $recent_rule, $udp_rule, $udp_rule2, $udp_recent_rule); +} + + + +1; + +# Local Variables: +# mode: perl +# indent-tabs-mode: nil +# perl-indent-level: 2 +# End: -- cgit v1.2.3