From 95e6c54dc0a523bc4221191a7795e5eb19711028 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 11 Oct 2011 14:30:42 +0700 Subject: Bug 5682: Add SrcNatRule.pm module for source NAT rules processing --- lib/Vyatta/SrcNatRule.pm | 385 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 lib/Vyatta/SrcNatRule.pm diff --git a/lib/Vyatta/SrcNatRule.pm b/lib/Vyatta/SrcNatRule.pm new file mode 100644 index 0000000..0efbe8f --- /dev/null +++ b/lib/Vyatta/SrcNatRule.pm @@ -0,0 +1,385 @@ +# +# Module: SrcNatRule.pm +# +# **** 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) 2009 Vyatta, Inc. +# All Rights Reserved. +# +# Author: eng@vyatta.com +# Date: 2011 +# Description: Source NAT rule handling library +# +# **** End License **** +# + +package Vyatta::SrcNatRule; + +use strict; +use lib "/opt/vyatta/share/perl5"; +require Vyatta::Config; +require Vyatta::IpTables::AddressFilter; +use Vyatta::Misc; +use Vyatta::TypeChecker; +use Vyatta::NatRuleCommon; + +my $src = new Vyatta::IpTables::AddressFilter; +my $dst = new Vyatta::IpTables::AddressFilter; + +my %fields = ( + _rule_number => undef, + _outbound_if => undef, + _proto => undef, + _exclude => undef, + _disable => undef, + _log => undef, + _outside_addr => { + _addr => undef, + _range => { + _start => undef, + _stop => undef, + }, + _port => undef, + }, + _is_masq => undef +); + +my $type = "SRC"; + +sub new { + my $that = shift; + my $class = ref ($that) || $that; + my $self = { + %fields, + }; + + bless $self, $class; + return $self; +} + +sub setup { + my ( $self, $level ) = @_; + my $config = new Vyatta::Config; + + $config->setLevel("$level"); + $self->{_rule_number} = $config->returnParent(".."); + $self->{_outbound_if} = $config->returnValue("outbound-interface"); + $self->{_proto} = $config->returnValue("protocol"); + $self->{_exclude} = $config->exists("exclude"); + $self->{_disable} = $config->exists("disable"); + $self->{_log} = $config->returnValue("log"); + + $self->{_outside_addr}->{_addr} + = $config->returnValue("outside-address address"); + if (defined($self->{_outside_addr}->{_addr}) && ($self->{_outside_addr}->{_addr} eq "masquerade")) { + $self->{_is_masq} = 1; + } + $self->{_outside_addr}->{_range}->{_start} = undef; + $self->{_outside_addr}->{_range}->{_stop} = undef; + if (defined($self->{_outside_addr}->{_addr}) + && $self->{_outside_addr}->{_addr} =~ /^([^-]+)-([^-]+)$/) { + $self->{_outside_addr}->{_range}->{_start} = $1; + $self->{_outside_addr}->{_range}->{_stop} = $2; + $self->{_outside_addr}->{_addr} = undef; + } + $self->{_outside_addr}->{_port} + = $config->returnValue("outside-address port"); + + $src->setup("$level source"); + $dst->setup("$level destination"); + + return 0; +} + +sub setupOrig { + my ( $self, $level ) = @_; + my $config = new Vyatta::Config; + + $config->setLevel("$level"); + + $self->{_rule_number} = $config->returnParent(".."); + $self->{_outbound_if} = $config->returnOrigValue("outbound-interface"); + $self->{_proto} = $config->returnOrigValue("protocol"); + $self->{_exclude} = $config->existsOrig("exclude"); + $self->{_disable} = $config->existsOrig("disable"); + $self->{_log} = $config->returnOrigValue("log"); + + $self->{_outside_addr}->{_addr} + = $config->returnOrigValue("outside-address address"); + if (defined($self->{_outside_addr}->{_addr}) && ($self->{_outside_addr}->{_addr} eq "masquerade")) { + $self->{_is_masq} = 1; + } + $self->{_outside_addr}->{_range}->{_start} = undef; + $self->{_outside_addr}->{_range}->{_stop} = undef; + if (defined($self->{_outside_addr}->{_addr}) + && $self->{_outside_addr}->{_addr} =~ /^([^-]+)-([^-]+)$/) { + $self->{_outside_addr}->{_range}->{_start} = $1; + $self->{_outside_addr}->{_range}->{_stop} = $2; + $self->{_outside_addr}->{_addr} = undef; + } + $self->{_outside_addr}->{_port} + = $config->returnOrigValue("outside-address port"); + + $src->setupOrig("$level source"); + $dst->setupOrig("$level destination"); + + return 0; +} + +# returns (error, @rules) +sub rule_str { + my ($self) = @_; + my $rule_str = ""; + my $can_use_port = 1; + my $jump_target = ''; + my $jump_param = ''; + my $use_netmap = 0; + my $tcp_and_udp = 0; + + if (!defined($self->{_proto}) || + (($self->{_proto} ne "tcp_udp") + && ($self->{_proto} ne "tcp") && ($self->{_proto} ne "6") + && ($self->{_proto} ne "udp") && ($self->{_proto} ne "17"))) { + $can_use_port = 0; + } + + if ($self->{_exclude}) { + $jump_target = 'RETURN'; + } elsif (defined($self->{_is_masq})) { + $jump_target = 'MASQUERADE'; + } else { + $jump_target = 'SNAT'; + } + + if (defined($self->{_outbound_if})) { + if ($self->{_outbound_if} ne "any") { + $rule_str .= " -o $self->{_outbound_if}"; + } + } else { + # "masquerade" requires outbound_if. + # also make this a requirement for "source" to prevent users from + # inadvertently NATing loopback traffic. + return ('outbound-interface not specified', undef); + } + + if (defined($self->{_proto})) { + my $str = $self->{_proto}; + $str =~ s/^\!(.*)$/! $1/; + if ($str eq 'tcp_udp') { + $tcp_and_udp = 1; + $rule_str .= " -p tcp"; # we'll add the '-p udp' to 2nd rule later + } else { + $rule_str .= " -p $str"; + } + } + + my $to_src = ''; + if (defined($self->{_outside_addr}->{_addr})) { + + # Check outside-address + my $addr = $self->{_outside_addr}->{_addr}; + if (defined($self->{_is_masq})) { + # It's masquerade rule, outside address will not be used anyway + 1; + } elsif ($addr =~ m/\//) { + # Outside address is a probably x.x.x.x/y subnet thus it's a *-to-many rule + # Target will be NETMAP + return ("\"$addr\" is not a valid IPv4net address", undef) + if (!Vyatta::TypeChecker::validateType('ipv4net', $addr, 1)); + $to_src .= $addr; + $use_netmap = 1; + } else { + return ("\"$addr\" is not a valid IP address", undef) + if (!Vyatta::TypeChecker::validateType('ipv4', $addr, 1)); + $to_src .= $addr; + } + } elsif (defined($self->{_outside_addr}->{_range}->{_start}) + && defined($self->{_outside_addr}->{_range}->{_stop})) { + my $start = $self->{_outside_addr}->{_range}->{_start}; + my $stop = $self->{_outside_addr}->{_range}->{_stop}; + return ("\"$start-$stop\" is not a valid IP range", undef) + if (!Vyatta::TypeChecker::validateType('ipv4', $start, 1) + || !Vyatta::TypeChecker::validateType('ipv4', $stop, 1)); + $to_src .= "$start-$stop"; + } + + if (defined($self->{_outside_addr}->{_port})) { + if (!$can_use_port) { + return ("ports can only be specified when protocol is \"tcp\" " + . "\"udp\" or \"tcp_udp\" (currently \"$self->{_proto}\")", undef); + } + if ($use_netmap) { + return ("Cannot use ports with an IPv4net type outside-address as it " . + "statically maps a whole network of addresses onto another " . + "network of addresses", undef); + } + + if (!defined($self->{_is_masq})) { + $to_src .= ":"; + } + my ($success, $err) = (undef, undef); + my $port = $self->{_outside_addr}->{_port}; + if ($port =~ /-/) { + ($success, $err) + = Vyatta::Misc::isValidPortRange($port, '-'); + return ($err, undef) if (!defined($success)); + } elsif ($port =~ /^\d/) { + ($success, $err) + = Vyatta::Misc::isValidPortNumber($port); + return ($err, undef) if (!defined($success)); + } else { + if ($self->{_proto} eq 'tcp_udp') { + ($success, $err) = Vyatta::Misc::isValidPortName($port, 'tcp'); + return ($err, undef) if !defined $success ; + ($success, $err) = Vyatta::Misc::isValidPortName($port, 'udp'); + return ($err, undef) if !defined $success ; + $port = getservbyname($port, 'tcp'); + } else { + ($success, $err) = Vyatta::Misc::isValidPortName($port, $self->{_proto}); + return ($err, undef) if !defined $success ; + $port = getservbyname($port, $self->{_proto}); + } + } + $to_src .= "$port"; + } + + if ($self->{_exclude}) { + # outside-address has no effect for "exclude" rules + } elsif ($to_src ne '') { + if (defined($self->{_is_masq})) { + $jump_param .= " --to-ports $to_src"; + } else { + if ($use_netmap) { + # replace "SNAT" with "NETMAP" + $jump_target = 'NETMAP'; + $jump_param .= " --to $to_src"; + } else { + $jump_param .= " --to-source $to_src"; + } + } + } elsif (!defined($self->{_is_masq})) { + return ('outside-address not specified', undef); + } + + + # source rule string + my ($src_str, $src_err) = $src->rule(); + return ($src_err, undef) if (!defined($src_str)); + + # destination rule string + my ($dst_str, $dst_err) = $dst->rule(); + return ($dst_err, undef) if (!defined($dst_str)); + + if ((grep /multiport/, $src_str) || (grep /multiport/, $dst_str)) { + if ((grep /sport/, $src_str) && (grep /dport/, $dst_str)) { + return ('cannot specify multiple ports when both source and destination ' + . 'ports are specified', undef); + } + } + + # if using netmap then source address should have the same prefix + # as the outside|inside address depending on the whether the type is src|dst + if ($use_netmap) { + + if (!defined $src->{_network}){ + return ("\nsource address needs to be defined as a subnet with the same network prefix as outside-address" . + "\nwhen outside-address is defined with a prefix for static network mapping " + , undef); + } + + my $outside_addr_mask = $self->{_outside_addr}->{_addr}; + my $src_addr_mask = $src->{_network}; + $outside_addr_mask =~ s/.+\///; + $src_addr_mask =~ s/.+\///; + + if (!($outside_addr_mask == $src_addr_mask)) { + return ("\nsource address should be a subnet with the same network prefix as outside-address" . + "\nwhen outside-address is defined with a prefix for static network mapping " + , undef); + } + + if ($src->{_network} =~ /\!/) { + return ("\ncannot define a negated source address when outside-address" . + "\nis defined with a prefix for static network mapping " + , undef); + + } + } + + return (undef, undef) if defined $self->{_disable}; + + my $comment = "\"$type-NAT-$self->{_rule_number}\" "; + if ($tcp_and_udp == 1) { + $comment = "\"$type-NAT-$self->{_rule_number} tcp_udp\" "; + } + $rule_str .= " $src_str $dst_str" . " -m comment --comment " . $comment; + if ("$self->{_log}" eq "enable") { + my $rule_num = $self->{_rule_number}; + my $log_prefix = get_log_prefix($rule_num, $jump_target, $type); + if ($tcp_and_udp == 1) { + my $tcp_log_rule = $rule_str; + $tcp_log_rule .= " -j LOG --log-prefix \"$log_prefix\" "; + my $udp_log_rule = $tcp_log_rule; + $udp_log_rule =~ s/ \-p tcp / -p udp /; + $rule_str .= " -j $jump_target $jump_param"; + my $udp_rule_str = $rule_str; + $udp_rule_str =~ s/ \-p tcp / -p udp /; + return (undef, $tcp_log_rule, $rule_str, $udp_log_rule, $udp_rule_str); + } else { + my $log_rule = $rule_str; + $log_rule .= " -j LOG --log-prefix \"$log_prefix\" "; + $rule_str .= " -j $jump_target $jump_param"; + return (undef, $log_rule, $rule_str); + } + } else { + $rule_str .= " -j $jump_target $jump_param"; + if ($tcp_and_udp == 1) { + # protocol is 'tcp_udp'; make another rule for protocol 'udp' + my $udp_rule_str = $rule_str; + $udp_rule_str =~ s/ \-p tcp / -p udp /; + return (undef, $rule_str, $udp_rule_str); + } else { + return (undef, $rule_str); + } + } +} + +sub print_str { + my ($self) = @_; + my $str = + "out_if[$self->{_outbound_if}] " . + "proto[$self->{_proto}] " . + "outaddr[$self->{_outside_addr}->{_addr}] " . + "outrange[$self->{_outside_addr}->{_range}->{_start}-" . + "$self->{_outside_addr}->{_range}->{_stop}]" . + "outp[$self->{_outside_addr}->{_port}] "; + + return $str; +} + +sub outputXml { + my ($self, $fh) = @_; + outputXmlElem("out_interface", $self->{_outbound_if}, $fh); + outputXmlElem("out_addr", $self->{_outside_addr}->{_addr}, $fh); + outputXmlElem("out_addr_start", $self->{_outside_addr}->{_range}->{_start}, + $fh); + outputXmlElem("out_addr_stop", $self->{_outside_addr}->{_range}->{_stop}, + $fh); + outputXmlElem("out_port", $self->{_outside_addr}->{_port}, $fh); + + $src->outputXml("src", $fh); + $dst->outputXml("dst", $fh); + # no proto? ($self->{_proto}) +} + +1; + -- cgit v1.2.3