summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2021-11-17 15:02:20 -0500
committerDaniil Baturin <daniil@vyos.io>2021-11-17 15:02:54 -0500
commitf29ab174f02eea5dc467823ed2ae67641ab476a4 (patch)
tree3e4337fbff4215a707b029b7146db62f7bc926f0
parente614230845a84fc478945cd1a56bfc5676c2038e (diff)
downloadvyatta-nat-f29ab174f02eea5dc467823ed2ae67641ab476a4.tar.gz
vyatta-nat-f29ab174f02eea5dc467823ed2ae67641ab476a4.zip
T1083: add port and address mapping options
-rw-r--r--lib/SrcNatRule.pm422
-rw-r--r--lib/Vyatta/DstNatRule.pm20
-rw-r--r--lib/Vyatta/SrcNatRule.pm32
-rw-r--r--templates-cfg/nat/destination/rule/node.tag/translation/options/address-mapping/node.def6
-rw-r--r--templates-cfg/nat/destination/rule/node.tag/translation/options/node.def1
-rw-r--r--templates-cfg/nat/source/rule/node.tag/translation/options/address-mapping/node.def6
-rw-r--r--templates-cfg/nat/source/rule/node.tag/translation/options/node.def1
-rw-r--r--templates-cfg/nat/source/rule/node.tag/translation/options/port-mapping/node.def6
8 files changed, 493 insertions, 1 deletions
diff --git a/lib/SrcNatRule.pm b/lib/SrcNatRule.pm
new file mode 100644
index 0000000..6f354e5
--- /dev/null
+++ b/lib/SrcNatRule.pm
@@ -0,0 +1,422 @@
+#
+# 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("translation 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("translation port");
+
+ $self->{_address_mapping} = $config->returnValue("translation options address-mapping");
+ $self->{_port_mapping} = $config->returnValue("translation options port-mapping");
+
+ $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("translation 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("translation port");
+
+ $self->{_address_mapping} = $config->returnOrigValue("translation options address-mapping");
+ $self->{_port_mapping} = $config->returnOrigValue("translation options port-mapping");
+
+ $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 $log_modifier = '';
+ 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';
+ $log_modifier = 'EXCL';
+ } elsif (defined($self->{_is_masq})) {
+ $jump_target = 'MASQUERADE';
+ $log_modifier = 'MASQ';
+ } 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};
+ my $negate = "";
+ if ($str =~ /^\!(.*)$/) {
+ $str = $1;
+ $negate = "!";
+ }
+ 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 .= " $negate -p $str ";
+ }
+ }
+
+ my $to_src = '';
+ if (defined($self->{_outside_addr}->{_addr})) {
+
+ # Check translation 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/\//) {
+ # Translation 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));
+
+ print("Warning: IP address $addr does not exist on the system!\n")
+ if !(is_local_address($addr));
+
+ $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 translation 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";
+ }
+
+ my $port_mapping = $self->{_port_mapping};
+ if(defined($port_mapping) && !defined($self->{_is_masq})) {
+ return ('port-mapping option is only valid for masquerade rules', undef);
+ }
+
+ my $addr_mapping = $self->{_address_mapping};
+ if(defined($addr_mapping) && defined($self->{_is_masq})) {
+ return ('address-mapping option is not valid for masquerade rules', undef);
+ }
+
+ if ($self->{_exclude}) {
+ # translation 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";
+
+ my $addr_mapping = $self->{_address_mapping};
+ if(defined($addr_mapping)) {
+ if($addr_mapping eq "persistent") {
+ $jump_param .= " --persistent";
+ } elsif ($addr_mapping eq "random") {
+ # random is the default, do nothing
+ } else {
+ return ('address-mapping must be either "persistent" or "random"', undef);
+ }
+ }
+ }
+ }
+ } elsif (!defined($self->{_is_masq})) {
+ return ('translation address not specified', undef);
+ }
+
+ if(defined($port_mapping) && defined($self->{_is_masq})) {
+ if($port_mapping eq "random") {
+ $jump_param .= " --random-fully";
+ } elsif ($port_mapping eq "none") {
+ # none is the deault, do nothing
+ } else {
+ return ('port-mapping must be either "random" or "none"', 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 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 translation address" .
+ "\nwhen translation 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 ($src->{_network} =~ /\!/) {
+ return ("\ncannot define a negated source address when translation 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\" ";
+ }
+ my $src_dst_str = make_src_dst_str($src_str, $dst_str);
+ $rule_str .= " $src_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, $type, $log_modifier);
+ 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;
+
diff --git a/lib/Vyatta/DstNatRule.pm b/lib/Vyatta/DstNatRule.pm
index aa419e5..98ebd56 100644
--- a/lib/Vyatta/DstNatRule.pm
+++ b/lib/Vyatta/DstNatRule.pm
@@ -91,6 +91,10 @@ sub setup {
}
$self->{_inside_addr}->{_port}
= $config->returnValue("translation port");
+
+ $self->{_address_mapping} = $config->returnValue("translation options address-mapping");
+ $self->{_port_mapping} = $config->returnValue("translation options port-mapping");
+
$src->setup("$level source");
$dst->setup("$level destination");
@@ -122,6 +126,9 @@ sub setupOrig {
}
$self->{_inside_addr}->{_port}
= $config->returnOrigValue("translation port");
+
+ $self->{_address_mapping} = $config->returnOrigValue("translation options address-mapping");
+ $self->{_port_mapping} = $config->returnOrigValue("translation options port-mapping");
$src->setupOrig("$level source");
$dst->setupOrig("$level destination");
@@ -248,7 +255,18 @@ sub rule_str {
$jump_target = 'NETMAP';
$jump_param .= " $to_dst";
} else {
- $jump_param .= " $to_dst";
+ $jump_param .= " $to_dst";
+
+ my $addr_mapping = $self->{_address_mapping};
+ if(defined($addr_mapping)) {
+ if($addr_mapping eq "persistent") {
+ $jump_param .= " --persistent";
+ } elsif ($addr_mapping eq "random") {
+ # random is the default, do nothing
+ } else {
+ return ('address-mapping must be either "persistent" or "random"', undef);
+ }
+ }
}
} else {
return ("translation address not specified", undef);
diff --git a/lib/Vyatta/SrcNatRule.pm b/lib/Vyatta/SrcNatRule.pm
index dfb6f28..dac868e 100644
--- a/lib/Vyatta/SrcNatRule.pm
+++ b/lib/Vyatta/SrcNatRule.pm
@@ -94,6 +94,9 @@ sub setup {
$self->{_outside_addr}->{_port}
= $config->returnValue("translation port");
+ $self->{_address_mapping} = $config->returnValue("translation options address-mapping");
+ $self->{_port_mapping} = $config->returnValue("translation options port-mapping");
+
$src->setup("$level source");
$dst->setup("$level destination");
@@ -129,6 +132,9 @@ sub setupOrig {
$self->{_outside_addr}->{_port}
= $config->returnOrigValue("translation port");
+ $self->{_address_mapping} = $config->returnOrigValue("translation options address-mapping");
+ $self->{_port_mapping} = $config->returnOrigValue("translation options port-mapping");
+
$src->setupOrig("$level source");
$dst->setupOrig("$level destination");
@@ -268,13 +274,39 @@ sub rule_str {
} elsif ($to_src ne '') {
if (defined($self->{_is_masq})) {
$jump_param .= " --to-ports $to_src";
+
+ my $port_mapping = $self->{_port_mapping};
+ if(defined($port_mapping)) {
+ if($port_mapping eq "random") {
+ $jump_param .= " --random-fully";
+ } elsif ($port_mapping eq "none") {
+ # none is the deault, do nothing
+ } else {
+ return ('port-mapping must be either "random" or "none"', undef);
+ }
+ }
} else {
+ if(defined($self->{_port_mapping})) {
+ return ('port-mapping option is only valid for masquerade rules', undef);
+ }
+
if ($use_netmap) {
# replace "SNAT" with "NETMAP"
$jump_target = 'NETMAP';
$jump_param .= " --to $to_src";
} else {
$jump_param .= " --to-source $to_src";
+
+ my $addr_mapping = $self->{_address_mapping};
+ if(defined($addr_mapping)) {
+ if($addr_mapping eq "persistent") {
+ $jump_param .= " --persistent";
+ } elsif ($addr_mapping eq "random") {
+ # random is the default, do nothing
+ } else {
+ return ('address-mapping must be either "persistent" or "random"', undef);
+ }
+ }
}
}
} elsif (!defined($self->{_is_masq})) {
diff --git a/templates-cfg/nat/destination/rule/node.tag/translation/options/address-mapping/node.def b/templates-cfg/nat/destination/rule/node.tag/translation/options/address-mapping/node.def
new file mode 100644
index 0000000..cf3df56
--- /dev/null
+++ b/templates-cfg/nat/destination/rule/node.tag/translation/options/address-mapping/node.def
@@ -0,0 +1,6 @@
+type: txt
+help: Address mapping options
+val_help: persistent; Gives a client the same source or destination-address for each connection
+val_help: random; Random source or destination address allocation for each connection (defaut)
+allowed: echo "persistent random"
+syntax:expression: exec "${vyos_libexec_dir}/validate-value --regex \'^(persistent|random)$\' --value \'$VAR(@)\'"; "Invalid value"
diff --git a/templates-cfg/nat/destination/rule/node.tag/translation/options/node.def b/templates-cfg/nat/destination/rule/node.tag/translation/options/node.def
new file mode 100644
index 0000000..51d5a0d
--- /dev/null
+++ b/templates-cfg/nat/destination/rule/node.tag/translation/options/node.def
@@ -0,0 +1 @@
+help: Translation options
diff --git a/templates-cfg/nat/source/rule/node.tag/translation/options/address-mapping/node.def b/templates-cfg/nat/source/rule/node.tag/translation/options/address-mapping/node.def
new file mode 100644
index 0000000..cf3df56
--- /dev/null
+++ b/templates-cfg/nat/source/rule/node.tag/translation/options/address-mapping/node.def
@@ -0,0 +1,6 @@
+type: txt
+help: Address mapping options
+val_help: persistent; Gives a client the same source or destination-address for each connection
+val_help: random; Random source or destination address allocation for each connection (defaut)
+allowed: echo "persistent random"
+syntax:expression: exec "${vyos_libexec_dir}/validate-value --regex \'^(persistent|random)$\' --value \'$VAR(@)\'"; "Invalid value"
diff --git a/templates-cfg/nat/source/rule/node.tag/translation/options/node.def b/templates-cfg/nat/source/rule/node.tag/translation/options/node.def
new file mode 100644
index 0000000..51d5a0d
--- /dev/null
+++ b/templates-cfg/nat/source/rule/node.tag/translation/options/node.def
@@ -0,0 +1 @@
+help: Translation options
diff --git a/templates-cfg/nat/source/rule/node.tag/translation/options/port-mapping/node.def b/templates-cfg/nat/source/rule/node.tag/translation/options/port-mapping/node.def
new file mode 100644
index 0000000..72f7d48
--- /dev/null
+++ b/templates-cfg/nat/source/rule/node.tag/translation/options/port-mapping/node.def
@@ -0,0 +1,6 @@
+type: txt
+help: Port mapping options
+val_help: random; Randomize source port mapping
+val_help: none; Do not apply port randomization (default)
+allowed: echo "random none"
+syntax:expression: exec "${vyos_libexec_dir}/validate-value --regex \'^(random|none)$\' --value \'$VAR(@)\'"; "Invalid value"