You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2014/12/09 22:11:38 UTC

svn commit: r1644185 [1/2] - in /vcl/trunk/managementnode/lib/VCL: ./ Module/ Module/OS/ Module/OS/Linux/ Module/OS/Linux/firewall/ Module/OS/Linux/init/

Author: arkurth
Date: Tue Dec  9 21:11:37 2014
New Revision: 1644185

URL: http://svn.apache.org/r1644185
Log:
VCL-174
Added backend support for NAT.

Added entries to $SUBROUTINE_MAPPINGS hash in DataStructure.pm for new NAT-related database tables.

Added subroutines:
-Module.pm::create_nathost_os_object
-Module.pm::nathost_os
-Module.pm::set_nathost_os subroutines.
-utils.pm::get_computer_nathost_info
-utils.pm::get_nathost_assigned_public_ports
-utils.pm::populate_reservation_natport
-utils.pm::insert_natport
-utils.pm::get_reservation_request_id
-utils.pm::get_reservation_request_info
-Linux.pm::firewall
-Linux.pm::enable_ip_forwarding

Updated utils.pm::get_connect_method_info to retrieve natport information.

Added code to State.pm::initialize to create a NAT host OS object if necessary.

Updated Module.pm::create_mn_os_object to pass the reservation ID to the DataStructure.pm constructor if it is available.

Updated utils.pm::get_request_info to populate the natport table.

Updated utils.pm::get_computer_info to also retrieve NAT host info.

Added modularized Linux firewall framework. This will eventually dynamically figure out which Linux firewall commands to use. For now, only iptables is supported. Added Linux/firewall directory and iptables.pm.

Added code to process_connect_methods to check if NAT is used. If so, a NAT port forwarding is added for each connect method port.

Added block to reclaim.pm::process to delete the NAT rules created for a reservation if necessary.

Updated all places that use results from connect_method_info to handle new format of hash returned.


Other
Fixed sloppy indentation in reclaim.pm. Please stop this nonsense.

Updated Module.pm::new to attempt to use an existing ManagementNode.pm object if one exists.

Added init.pm file, which is not the parent class of the Linux init modules. Updated Linux.pm::get_init_modules to pass a "base_package" argument to the init module constructor. Changed base package in init modules from VCL::Module::OS::Linux to VCL::Module::OS::Linux::init.

Added:
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init.pm
Modified:
    vcl/trunk/managementnode/lib/VCL/DataStructure.pm
    vcl/trunk/managementnode/lib/VCL/Module.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/SysV.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/systemd.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
    vcl/trunk/managementnode/lib/VCL/Module/State.pm
    vcl/trunk/managementnode/lib/VCL/new.pm
    vcl/trunk/managementnode/lib/VCL/reclaim.pm
    vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: vcl/trunk/managementnode/lib/VCL/DataStructure.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/DataStructure.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/DataStructure.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/DataStructure.pm Tue Dec  9 21:11:37 2014
@@ -246,6 +246,17 @@ $SUBROUTINE_MAPPINGS{computer_predictive
 $SUBROUTINE_MAPPINGS{computer_predictive_module_description} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{predictive}{module}{description}';
 $SUBROUTINE_MAPPINGS{computer_predictive_module_perl_package} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{predictive}{module}{perlpackage}';
 
+$SUBROUTINE_MAPPINGS{nathost_info} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}';
+$SUBROUTINE_MAPPINGS{nathost_hostname} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{HOSTNAME}';
+$SUBROUTINE_MAPPINGS{nathost_date_deleted} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{datedeleted}';
+$SUBROUTINE_MAPPINGS{nathost_deleted} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{deleted}';
+$SUBROUTINE_MAPPINGS{nathost_id} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{id}';
+$SUBROUTINE_MAPPINGS{nathost_nat_ip} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{natIP}';
+$SUBROUTINE_MAPPINGS{nathost_resource_id} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{resource}{id}';
+$SUBROUTINE_MAPPINGS{nathost_resource_subid} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{resource}{subid}';
+$SUBROUTINE_MAPPINGS{nathost_resourcetype_id} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{resource}{resourcetype}{id}';
+$SUBROUTINE_MAPPINGS{nathost_resource_type} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{nathost}{resource}{resourcetype}{name}';
+
 $SUBROUTINE_MAPPINGS{vmhost_computer_id} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{vmhost}{computerid}';
 $SUBROUTINE_MAPPINGS{vmhost_hostname} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{vmhost}{computer}{hostname}';
 $SUBROUTINE_MAPPINGS{vmhost_short_name} = '$self->request_data->{reservation}{RESERVATION_ID}{computer}{vmhost}{computer}{SHORTNAME}';
@@ -682,7 +693,7 @@ sub _initialize : Init {
 		}
 		$self->request_data->{reservation}{$self->reservation_id}{computer}{vmhost} = $vmhost_info;
 	}
-	
+
 	# If either the computer, image, or imagerevision identifier arguments are specified, retrieve appropriate image and imagerevision data
 	if (defined($imagerevision_identifier) || defined($image_identifier) || defined($computer_identifier)) {
 		my $imagerevision_info;
@@ -1126,46 +1137,45 @@ sub get_reservation_data {
 
 =head2 set_reservation_remote_ip
 
- Parameters  : None
- Returns     : string
- Description : 
+ Parameters  : $remote_ip
+ Returns     : boolean
+ Description : Updates the reservation.remoteIP value in the database.
 
 =cut
 
 sub set_reservation_remote_ip {
-   my $self = shift;
-   my $reservation_id  = $self->get_reservation_id();
+	my $self = shift;
+	my $reservation_id = $self->get_reservation_id();
 	
-	my $new_remote_ip = shift;
+	my $remote_ip = shift;
 	
 	# Check to make sure reservation ID was passed
-   if (!$new_remote_ip) {
-        notify($ERRORS{'WARNING'}, 0, "new_remote_ip was not specified, returning self");
-        return 0;;
-   }
-
-	
-	my $update_statement = "
-		  UPDATE
-		  reservation
-		  SET
-		  remoteIP = \'$new_remote_ip\'
-		  WHERE
-		  id = \'$reservation_id\'
-			  ";
-
-        # Call the database execute subroutine
-        if (database_execute($update_statement)) {
-                # Update successful
-                notify($ERRORS{'OK'}, 0, "new remoteIP $new_remote_ip for reservation id $reservation_id updated");
-                return 1;
-        }
-        else {
-                notify($ERRORS{'CRITICAL'}, 0, "unable to update new remote ip for reservation id $reservation_id");
-                return 0;
-        }
-
-
+	if (!$remote_ip) {
+		notify($ERRORS{'WARNING'}, 0, "remote IP address argument was not specified");
+		return 0;
+	}
+	
+	# Set the current value in the request data hash
+	$self->request_data->{reservation}{$reservation_id}{remoteIP} = $remote_ip;
+	
+	my $update_statement = <<EOF;
+UPDATE
+reservation
+SET
+remoteIP = '$remote_ip'
+WHERE
+id = '$reservation_id'
+EOF
+	
+	# Call the database execute subroutine
+	if (database_execute($update_statement)) {
+		notify($ERRORS{'OK'}, 0, "remote IP updated to $remote_ip for reservation $reservation_id");
+		return 1;
+	}
+	else {
+		notify($ERRORS{'CRITICAL'}, 0, "failed to update remote IP to $remote_ip for reservation $reservation_id");
+		return 0;
+	}
 } ## end sub set_reservation_remote_ip
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -1180,7 +1190,7 @@ sub set_reservation_remote_ip {
 
 sub get_reservation_remote_ip {
 	my $self = shift;
-	my $reservation_id  = $self->get_reservation_id();
+	my $reservation_id = $self->get_reservation_id();
 
 	# Create the select statement
 	my $select_statement = "

Modified: vcl/trunk/managementnode/lib/VCL/Module.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module.pm Tue Dec  9 21:11:37 2014
@@ -183,7 +183,7 @@ sub new {
 		my $request_state_name = $self->data->get_request_state_name(0) || '<not set>';
 		notify($ERRORS{'DEBUG'}, 0, ref($self) . " object created for state $request_state_name, address: $address");
 	}
-	elsif ($self->isa('VCL::Module::OS')) {
+	elsif ($self->isa('VCL::Module::OS') && !$self->isa('VCL::Module::OS::Linux::ManagementNode')) {
 		my $image_name = $self->data->get_image_name(0) || '<not set>';
 		notify($ERRORS{'DEBUG'}, 0, ref($self) . " object created for image $image_name, address: $address");
 	}
@@ -203,8 +203,11 @@ sub new {
 		if ($args->{mn_os}) {
 			$mn_os = $args->{mn_os};
 		}
+		elsif ($self->mn_os(0)) {
+			$mn_os = $self->mn_os();
+		}
 		else {
-			$mn_os = $self->create_mn_os_object()
+			$mn_os = $self->create_mn_os_object();
 		}
 		
 		if ($mn_os) {
@@ -316,10 +319,17 @@ sub create_object {
 	my $perl_package = $argument;
 	
 	my $data;
-	if (my $data_structure_arguments = shift || !$self) {
+	my $data_structure_arguments = shift;
+	if ($data_structure_arguments) {
+		notify($ERRORS{'DEBUG'}, 0, "new DataStructure object will be created for the $perl_package object, data structure arguments passed:\n" . format_data($data_structure_arguments));
 		$data = create_datastructure_object($data_structure_arguments);
 	}
+	elsif (!$self) {
+		notify($ERRORS{'DEBUG'}, 0, "new DataStructure object will be created for the $perl_package object, data structure arguments not passed and not called as an object reference");
+		$data = create_datastructure_object();
+	}
 	elsif ($self) {
+		notify($ERRORS{'DEBUG'}, 0, "existing DataStructure object will be passed to the new $perl_package object");
 		$data = $self->data;
 	}
 
@@ -441,10 +451,24 @@ sub create_os_object {
 =cut
 
 sub create_mn_os_object {
+	my $self = shift;
+	
+	my $datastructure_arguments = {
+		'image_identifier' => 'noimage'
+	};
+	
+	# Check if called as an object reference
+	if ($self && ref($self) =~ /VCL/) {
+		# Add the reservation ID to the DataStructure arguments
+		# Otherwise, get_reservation_id won't be available
+		my $reservation_id = $self->data->get_reservation_id();
+		$datastructure_arguments->{reservation_id} = $reservation_id;
+	}
+	
 	# Create a DataStructure object containing computer data for the management node
 	my $mn_data;
 	eval {
-		$mn_data = new VCL::DataStructure('image_identifier' => 'noimage');
+		$mn_data = new VCL::DataStructure($datastructure_arguments);
 	};
 	
 	# Attempt to load the OS module
@@ -572,6 +596,83 @@ sub create_vmhost_os_object {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 create_nathost_os_object
+
+ Parameters  : none
+ Returns     : VCL::Module::OS object reference
+ Description : Creates an OS module object to control the reservation computer's
+               NAT host.
+
+=cut
+
+sub create_nathost_os_object {
+my $self = shift;
+	unless (ref($self) && $self->isa('VCL::Module')) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	# Check if an OS object has already been stored in the calling object
+	if (my $nathost_os = $self->nathost_os(0)) {
+		return $nathost_os;
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "attempting to create NAT host OS object");
+	
+	# Make sure calling object isn't an OS module to avoid an infinite loop
+	if ($self->isa('VCL::Module::OS')) {
+		notify($ERRORS{'WARNING'}, 0, "this subroutine cannot be called from an existing OS module: " . ref($self));
+		return;
+	}
+	
+	# Make sure computer is mapped to a NAT host
+	my $nathost_id = $self->data->get_nathost_id();
+	if (!$nathost_id) {
+		notify($ERRORS{'WARNING'}, 0, "NAT host OS object not created, computer is not mapped to a NAT host");
+		return;
+	}
+	
+	my $request_data = $self->data->get_request_data();
+	my $reservation_id = $self->data->get_reservation_id();
+	
+	my $nathost_hostname = $self->data->get_nathost_hostname();
+	my $nathost_resource_subid = $self->data->get_nathost_resource_subid();
+	my $nathost_resource_type = $self->data->get_nathost_resource_type();
+	if ($nathost_resource_type eq 'managementnode') {
+		notify($ERRORS{'DEBUG'}, 0, "NAT host resource type is $nathost_resource_type, returning management node OS object to control $nathost_hostname");
+		return $self->mn_os();
+	}
+	elsif ($nathost_resource_type eq 'computer') {
+		# Get the computer info in order to determine the OS module to use
+		my $computer_info = get_computer_info($nathost_resource_subid);
+		if (!$computer_info) {
+			notify($ERRORS{'WARNING'}, 0, "failed to create NAT host OS object, failed to retrieve info for computer ID: $nathost_resource_subid, NAT host info:\n" . format_data($self->data->get_nathost_info()));
+		}
+		my $computer_os_package = $computer_info->{currentimagerevision}{image}{OS}{module}{perlpackage};
+		
+		notify($ERRORS{'DEBUG'}, 0, "NAT host resource type is $nathost_resource_type, creating $computer_os_package OS object to control $nathost_hostname");
+		
+		my $nathost_os = $self->create_object($computer_os_package, {
+			#request_data => $request_data,
+			reservation_id => $reservation_id,
+			computer_identifier => $nathost_resource_subid
+		});
+		if ($nathost_os) {
+			return $nathost_os;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "failed to create NAT host OS object to control $nathost_hostname");
+			return;
+		}
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "unable to create NAT host OS object to control $nathost_hostname, NAT host resource type is not supported: $nathost_resource_type, NAT host info:\n" . format_data($self->data->get_nathost_info()));
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 create_provisioning_object
 
  Parameters  : none
@@ -807,6 +908,39 @@ sub vmhost_os {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 nathost_os
+
+ Parameters  : $display_warning (optional)
+ Returns     : NAT hosts's OS object
+ Description : Allows modules to access the NAT host's OS object.
+
+=cut
+
+sub nathost_os {
+	my $self = shift;
+	if (!ref($self) || !$self->isa('VCL::Module')) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was not called as a VCL::Module or VCL::DataStructure class method");
+		return;
+	}
+	
+	my $display_warning = shift;
+	if (!defined($display_warning)) {
+		$display_warning = 1;
+	}
+	
+	if (!$self->{nathost_os}) {
+		if ($display_warning) {
+			notify($ERRORS{'WARNING'}, 0, "unable to return NAT host OS object, \$self->{nathost_os} is not set");
+		}
+		return;
+	}
+	else {
+		return $self->{nathost_os};
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 set_data
 
  Parameters  : $data
@@ -935,6 +1069,41 @@ sub set_vmhost_os {
 	return 1;
 }
 
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 set_nathost_os
+
+ Parameters  : $nathost_os
+ Returns     : boolean
+ Description : Sets the NAT host OS object for the module to access.
+
+=cut
+
+sub set_nathost_os {
+	my $self = shift;
+	if (!ref($self) || !$self->isa('VCL::Module')) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was not called as a VCL::Module class method");
+		return;
+	}
+	
+	my $nathost_os = shift;
+	if (!defined($nathost_os)) {
+		notify($ERRORS{'WARNING'}, 0, "OS object reference argument not supplied");
+		return;
+	}
+	elsif (!ref($nathost_os) || !$nathost_os->isa('VCL::Module')) {
+		notify($ERRORS{'WARNING'}, 0, "supplied argument is not a VCL::Module object reference:\n" . format_data($nathost_os));
+		return;
+	}
+	
+	my $address = sprintf('%x', $self);
+	my $type = ref($self);
+	my $nathost_os_address = sprintf('%x', $nathost_os);
+	notify($ERRORS{'DEBUG'}, 0, "storing reference to NAT host OS object (address: $nathost_os_address) in this $type object (address: $address)");
+	$self->{nathost_os} = $nathost_os;
+	return 1;
+}
+
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 set_provisioner

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS.pm Tue Dec  9 21:11:37 2014
@@ -2111,7 +2111,7 @@ sub remove_lines_from_file {
 =cut
 
 sub execute {
-#return execute_new(@_);
+return execute_new(@_);
 	my ($argument) = @_;
 	my ($computer_name, $command, $display_output, $timeout_seconds, $max_attempts, $port, $user, $password, $identity_key, $ignore_error);
 	
@@ -2653,6 +2653,8 @@ sub process_connect_methods {
 		return;
 	}
 	
+	my $reservation_id = $self->data->get_reservation_id();
+	my $request_state = $self->data->get_request_state_name();
 	my $computer_node_name = $self->data->get_computer_node_name();
 	
 	# Retrieve the connect method info hash
@@ -2661,7 +2663,26 @@ sub process_connect_methods {
 		notify($ERRORS{'WARNING'}, 0, "failed to retrieve connect method info");
 		return;
 	}
-
+	
+	# Check if NAT is used
+	my $nathost_hostname;
+	my $computer_private_ip_address;
+	if ($self->nathost_os(0)) {
+		$nathost_hostname = $self->data->get_nathost_hostname();
+		# Call configure_nat - this adds a chain for the reservation if one does not already exist
+		if (!$self->nathost_os->firewall->configure_nat()) {
+			notify($ERRORS{'WARNING'}, 0, "failed to configure NAT on $nathost_hostname");
+			return;
+		}
+		
+		# Retrieve the computer's private IP address
+		$computer_private_ip_address = $self->get_private_ip_address();
+		if (!$computer_private_ip_address) {
+			notify($ERRORS{'WARNING'}, 0, "failed to retrieve private IP address of computer $computer_node_name, unable to configure NAT port forwarding");
+			return;
+		}
+	}
+	
 	my $remote_ip = shift;
 	if (!$remote_ip) {
 		notify($ERRORS{'OK'}, 0, "reservation remote IP address is not defined, connect methods will be available from any IP address");
@@ -2681,11 +2702,8 @@ sub process_connect_methods {
 		$overwrite = 0;
 	}
 	
-	my $request_state = $self->data->get_request_state_name();
-	
 	CONNECT_METHOD: for my $connect_method_id (sort keys %{$connect_method_info} ) {
 		my $connect_method = $connect_method_info->{$connect_method_id};
-		#notify($ERRORS{'DEBUG'}, 0, "processing connect method:\n" . format_data($connect_method_info->{$connect_method_id}));
 		
 		my $name            = $connect_method->{name};
 		my $description     = $connect_method->{description};
@@ -2693,7 +2711,6 @@ sub process_connect_methods {
 		my $startup_script  = $connect_method->{startupscript};
 		my $install_script  = $connect_method->{installscript};
 		my $disabled        = $connect_method->{connectmethodmap}{disabled};
-
 		
 		if ($disabled || $request_state =~ /deleted|timeout/) {
 			if ($self->service_exists($service_name)) {
@@ -2704,11 +2721,11 @@ sub process_connect_methods {
 			
 			# Close the firewall ports
 			if ($self->can('disable_firewall_port')) {
-				for my $protocol (keys %{$connect_method->{connectmethodport}}) {
-					for my $port (keys %{$connect_method->{connectmethodport}{$protocol}}) {
-						if (!$self->disable_firewall_port($protocol, $port, $remote_ip, 1)) {
-							notify($ERRORS{'WARNING'}, 0, "failed to close firewall port $port on $computer_node_name for $remote_ip $name connect method");
-						}
+				for my $connect_method_port_id (keys %{$connect_method->{connectmethodport}}) {
+					my $protocol = $connect_method->{connectmethodport}{$connect_method_port_id}{protocol};
+					my $port = $connect_method->{connectmethodport}{$connect_method_port_id}{port};
+					if (!$self->disable_firewall_port($protocol, $port, $remote_ip, 1)) {
+						notify($ERRORS{'WARNING'}, 0, "failed to close firewall port $protocol/$port on $computer_node_name for $remote_ip $name connect method");
 					}
 				}
 			}
@@ -2752,15 +2769,35 @@ sub process_connect_methods {
 				}
 			}
 			
-			# Open the firewall ports
-			if ($self->can('enable_firewall_port')) {
-				for my $protocol (keys %{$connect_method->{connectmethodport}}) {
-					for my $port (keys %{$connect_method->{connectmethodport}{$protocol}}) {
-						if (!$self->enable_firewall_port($protocol, $port, $remote_ip, 1)) {
-							notify($ERRORS{'WARNING'}, 0, "failed to open firewall port $port on $computer_node_name for $remote_ip $name connect method");
-						}
+			for my $connect_method_port_id (keys %{$connect_method->{connectmethodport}}) {
+				my $protocol = $connect_method->{connectmethodport}{$connect_method_port_id}{protocol};
+				my $port = $connect_method->{connectmethodport}{$connect_method_port_id}{port};
+				
+				# Open the firewall port
+				if ($self->can('enable_firewall_port')) {
+					if (!$self->enable_firewall_port($protocol, $port, $remote_ip, 1)) {
+						notify($ERRORS{'WARNING'}, 0, "failed to open firewall port $protocol/$port on $computer_node_name for $remote_ip $name connect method");
+					}
+				}
+				
+				my $nat_public_port = $connect_method->{connectmethodport}{$connect_method_port_id}{natport}{publicport};
+				if ($nat_public_port) {
+					if (!$self->nathost_os(0)) {
+						notify($ERRORS{'WARNING'}, 0, "connect method info contains NAT port information but NAT OS object is not available to control $nathost_hostname");
+						return;
+					}
+					if ($self->nathost_os->firewall->add_nat_port_forward($protocol, $nat_public_port, $computer_private_ip_address, $port, $reservation_id)) {
+						notify($ERRORS{'OK'}, 0, "configured forwarded NAT port on $nathost_hostname: $protocol/$nat_public_port --> $computer_private_ip_address:$port");
+					}
+					else {
+						notify($ERRORS{'WARNING'}, 0, "failed to process '$name' connect method, unable to configure forwarded NAT port on $nathost_hostname: $protocol/$nat_public_port --> $computer_private_ip_address:$port");
+						return;
 					}
 				}
+				elsif ($self->nathost_os(0)) {
+					notify($ERRORS{'WARNING'}, 0, "NAT OS object is not available but connect method info does not contain NAT port information:\n" . format_data($connect_method_info));
+					return;
+				}
 			}
 		}
 	}
@@ -2800,15 +2837,16 @@ sub is_user_connected {
 	foreach my $connect_method_id (keys %$connect_methods) {
 		my $connect_method = $connect_methods->{$connect_method_id};
 		my $name = $connect_method->{name};
-		for my $protocol (keys %{$connect_method->{connectmethodport}}) {
-			for my $port (keys %{$connect_method->{connectmethodport}{$protocol}}) {
-				notify($ERRORS{'DEBUG'}, 0, "checking '$name' connect method, protocol: $protocol, port: $port");
-				
-				my $result = $self->check_connection_on_port($port);
-				if ($result && $result !~ /no/i) {
-					notify($ERRORS{'OK'}, 0, "$user_login_id is connected to $computer_node_name using $name connect method, result: $result");
-					return 1;
-				}
+		
+		for my $connect_method_port_id (keys %{$connect_method->{connectmethodport}}) {
+			my $protocol = $connect_method->{connectmethodport}{$connect_method_port_id}{protocol};
+			my $port = $connect_method->{connectmethodport}{$connect_method_port_id}{port};
+			
+			notify($ERRORS{'DEBUG'}, 0, "checking '$name' connect method, protocol: $protocol, port: $port");
+			my $result = $self->check_connection_on_port($port);
+			if ($result && $result !~ /no/i) {
+				notify($ERRORS{'OK'}, 0, "$user_login_id is connected to $computer_node_name using $name connect method, result: $result");
+				return 1;
 			}
 		}
 	}
@@ -3544,54 +3582,54 @@ sub get_connect_method_remote_ip_address
 	my @remote_ip_addresses = ();
 	
 	my $connect_method_info = $self->data->get_connect_methods();
-	foreach my $connect_method_id (keys %$connect_method_info) {
-		my $connect_method = $connect_method_info->{$connect_method_id};
-		my $connect_method_name = $connect_method->{name};
+	for my $connect_method_id (keys %$connect_method_info) {
+		my $connect_method_name = $connect_method_info->{$connect_method_id}{name};
 		
-		for my $connect_method_protocol (keys %{$connect_method->{connectmethodport}}) {
-			for my $connect_method_port (keys %{$connect_method->{connectmethodport}{$connect_method_protocol}}) {
-				notify($ERRORS{'DEBUG'}, 0, "checking connect method: '$connect_method_name', protocol: $connect_method_protocol, port: $connect_method_port");
-				
-				CONNECTION_PROTOCOL: for my $connection_protocol (keys %$connection_info) {
-					# Check if the protocol defined for the connect method matches the established connection
-					if (!$connect_method_protocol || $connect_method_protocol =~ /(\*|any|all)/i) {
-						#notify($ERRORS{'DEBUG'}, 0, "skipping validation of connect method protocol: $connect_method_protocol");
+		for my $connect_method_port_id (sort keys %{$connect_method_info->{$connect_method_id}{connectmethodport}}) {
+			my $connect_method_protocol = $connect_method_info->{$connect_method_id}{connectmethodport}{$connect_method_port_id}{protocol};
+			my $connect_method_port = $connect_method_info->{$connect_method_id}{connectmethodport}{$connect_method_port_id}{port};
+			
+			notify($ERRORS{'DEBUG'}, 0, "checking connect method: '$connect_method_name', protocol: $connect_method_protocol, port: $connect_method_port");
+			
+			CONNECTION_PROTOCOL: for my $connection_protocol (keys %$connection_info) {
+				# Check if the protocol defined for the connect method matches the established connection
+				if (!$connect_method_protocol || $connect_method_protocol =~ /(\*|any|all)/i) {
+					#notify($ERRORS{'DEBUG'}, 0, "skipping validation of connect method protocol: $connect_method_protocol");
+				}
+				else {
+					if ($connect_method_protocol =~ /$connection_protocol/i || $connection_protocol =~ /$connect_method_protocol/i) {
+						notify($ERRORS{'DEBUG'}, 0, "connect method protocol matches established connection protocol: $connection_protocol");
 					}
 					else {
-						if ($connect_method_protocol =~ /$connection_protocol/i || $connection_protocol =~ /$connect_method_protocol/i) {
-							notify($ERRORS{'DEBUG'}, 0, "connect method protocol matches established connection protocol: $connection_protocol");
-						}
-						else {
-							notify($ERRORS{'DEBUG'}, 0, "connect method protocol $connect_method_protocol does NOT match established connection protocol $connection_protocol");
-							next CONNECTION_PROTOCOL;
-						}
+						notify($ERRORS{'DEBUG'}, 0, "connect method protocol $connect_method_protocol does NOT match established connection protocol $connection_protocol");
+						next CONNECTION_PROTOCOL;
 					}
-					
-					CONNECTION_PORT: for my $connection_port (keys %{$connection_info->{$connection_protocol}}) {
-						# Check if the port defined for the connect method matches the established connection
-						if ($connect_method_port eq $connection_port) {
-							notify($ERRORS{'DEBUG'}, 0, "connect method port matches established connection port: $connection_port");
-							
-							for my $connection (@{$connection_info->{$connection_protocol}{$connection_port}}) {
-								my $remote_ip_address = $connection->{remote_ip};
-								if (!$remote_ip_address) {
-									notify($ERRORS{'WARNING'}, 0, "connection does NOT contain remote IP address (remote_ip) key:\n" . format_data($connection));
-								}
-								elsif ($remote_ip_address eq $mn_private_ip_address || $remote_ip_address eq $mn_public_ip_address) {
-									notify($ERRORS{'DEBUG'}, 0, "ignoring connection to port $connection_port from management node: $remote_ip_address");
-								}
-								elsif (my ($ignored_remote_ip_address) = grep { $remote_ip_address =~ /($_)/ } @ignored_remote_ip_addresses) {
-									notify($ERRORS{'DEBUG'}, 0, "ignoring connection to port $connection_port from ignored remote IP address ($ignored_remote_ip_address): $remote_ip_address");
-								}
-								else {
-									push @remote_ip_addresses, $remote_ip_address;
-								}
+				}
+				
+				CONNECTION_PORT: for my $connection_port (keys %{$connection_info->{$connection_protocol}}) {
+					# Check if the port defined for the connect method matches the established connection
+					if ($connect_method_port eq $connection_port) {
+						notify($ERRORS{'DEBUG'}, 0, "connect method port matches established connection port: $connection_port");
+						
+						for my $connection (@{$connection_info->{$connection_protocol}{$connection_port}}) {
+							my $remote_ip_address = $connection->{remote_ip};
+							if (!$remote_ip_address) {
+								notify($ERRORS{'WARNING'}, 0, "connection does NOT contain remote IP address (remote_ip) key:\n" . format_data($connection));
+							}
+							elsif ($remote_ip_address eq $mn_private_ip_address || $remote_ip_address eq $mn_public_ip_address) {
+								notify($ERRORS{'DEBUG'}, 0, "ignoring connection to port $connection_port from management node: $remote_ip_address");
+							}
+							elsif (my ($ignored_remote_ip_address) = grep { $remote_ip_address =~ /($_)/ } @ignored_remote_ip_addresses) {
+								notify($ERRORS{'DEBUG'}, 0, "ignoring connection to port $connection_port from ignored remote IP address ($ignored_remote_ip_address): $remote_ip_address");
+							}
+							else {
+								push @remote_ip_addresses, $remote_ip_address;
 							}
 						}
-						else {
-							notify($ERRORS{'DEBUG'}, 0, "connect method port $connect_method_port does NOT match established connection port $connection_port");
-							next CONNECTION_PORT;
-						}
+					}
+					else {
+						notify($ERRORS{'DEBUG'}, 0, "connect method port $connect_method_port does NOT match established connection port $connection_port");
+						next CONNECTION_PORT;
 					}
 				}
 			}
@@ -3615,8 +3653,8 @@ sub get_connect_method_remote_ip_address
 
  Parameters  : none
  Returns     : boolean
- Description : Opens the firewall port for the remote IP address for each
-               connect method.
+ Description : Updates the firewall to allow traffic to the address stored in
+               reservation remoteIP for each connection method.
 
 =cut
 
@@ -3627,11 +3665,13 @@ sub firewall_compare_update {
       return;
    }
 	
-	# Make sure the OS module implements an enable_firewall_port subroutine
+	# Make sure the OS module implements get_firewall_configuration and enable_firewall_port subroutine
 	return 1 unless $self->can('enable_firewall_port');
+	return 1 unless $self->can('get_firewall_configuration');
 	
    my $computer_node_name = $self->data->get_computer_node_name();
-   my $remote_ip = $self->data->get_reservation_remote_ip();
+	
+	my $remote_ip = $self->data->get_reservation_remote_ip();
 	if (!$remote_ip) {
 		notify($ERRORS{'WARNING'}, 0, "unable to update firewall on $computer_node_name, remote IP could not be retrieved for reservation");
       return;
@@ -3640,27 +3680,32 @@ sub firewall_compare_update {
    # Retrieve the connect method info
    my $connect_method_info = $self->data->get_connect_methods();
    if (!$connect_method_info) {
-      notify($ERRORS{'WARNING'}, 0, "failed to retrieve connect method information");
+      notify($ERRORS{'WARNING'}, 0, "failed to retrieve connect method info");
       return;
    }
 	
-   # Loop through the connect methods, check to make sure firewall is open for remote IP
+   # Retrieve the firewall configuration from the computer
+   my $firewall_configuration = $self->get_firewall_configuration() || return;
+	
+	# Loop through the connect methods, check to make sure firewall is open for remote IP
 	my $error_encountered = 0;
-   for my $connect_method_id (sort keys %{$connect_method_info}) {
-      my $connect_method_name = $connect_method_info->{$connect_method_id}{name};
-		for my $protocol (keys %{$connect_method_info->{$connect_method_id}{connectmethodport}}) {
-			for my $port (keys %{$connect_method_info->{$connect_method_id}{connectmethodport}{$protocol}}) {
-				if ($self->enable_firewall_port($protocol, $port, $remote_ip, 0)) {
-					notify($ERRORS{'DEBUG'}, 0, "$connect_method_name: processed firewall port $protocol $port on $computer_node_name for remote IP address: $remote_ip");
-				}
-				else {
-					$error_encountered = 1;
-					notify($ERRORS{'WARNING'}, 0, "$connect_method_name: failed to process firewall port $protocol $port on $computer_node_name for remote IP address: $remote_ip");
-				}
+	for my $connect_method_id (sort keys %$connect_method_info) {
+		my $connect_method_name = $connect_method_info->{$connect_method_id}{name};
+		
+		for my $connect_method_port_id (sort keys %{$connect_method_info->{$connect_method_id}{connectmethodport}}) {
+			my $connect_method_port = $connect_method_info->{$connect_method_id}{connectmethodport}{$connect_method_port_id};
+			my $protocol = $connect_method_info->{$connect_method_id}{connectmethodport}{$connect_method_port_id}{protocol};
+			my $port = $connect_method_info->{$connect_method_id}{connectmethodport}{$connect_method_port_id}{port};
+			
+			if ($self->enable_firewall_port($protocol, $port, $remote_ip, 0)) {
+				notify($ERRORS{'DEBUG'}, 0, "$connect_method_name: processed firewall port $protocol $port on $computer_node_name for remote IP address: $remote_ip");
+			}
+			else {
+				$error_encountered = 1;
+				notify($ERRORS{'WARNING'}, 0, "$connect_method_name: failed to process firewall port $protocol $port on $computer_node_name for remote IP address: $remote_ip");
 			}
 		}
 	}
-	
 	return !$error_encountered;
 }
 

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Tue Dec  9 21:11:37 2014
@@ -164,7 +164,7 @@ sub get_init_modules {
 		# initialize will check the computer to determine if it contains the corresponding Linux init daemon installed
 		# If not installed, the constructor will return false
 		my $init;
-		eval { $init = ($init_perl_package)->new({data_structure => $self->data, os => $self, mn_os => $self->mn_os}) };
+		eval { $init = ($init_perl_package)->new({data_structure => $self->data, os => $self, mn_os => $self->mn_os, base_package => ref($self)}) };
 		if ($init) {
 			my @required_commands = eval "@" . $init_perl_package . "::REQUIRED_COMMANDS";
 			if ($EVAL_ERROR) {
@@ -223,6 +223,75 @@ sub get_init_modules {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 firewall
+
+ Parameters  : none
+ Returns     : Linux firewall module reference
+ Description : Determines the Linux firewall module to use and creates an
+               object.
+
+=cut
+
+sub firewall {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+
+	return $self->{firewall} if $self->{firewall};
+	
+	notify($ERRORS{'DEBUG'}, 0, "beginning Linux firewall daemon module initialization");
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	# Get the absolute path of the init module directory
+	my $firewall_directory_path = "$FindBin::Bin/../lib/VCL/Module/OS/Linux/firewall";
+	notify($ERRORS{'DEBUG'}, 0, "Linux firewall module directory path: $firewall_directory_path");
+	
+	# Get a list of all *.pm files in the firewall module directory
+	my @firewall_module_paths = $self->mn_os->find_files($firewall_directory_path, '*.pm');
+	
+	# Attempt to create an initialize an object for each firewall module
+	my %firewall_module_hash;
+	FIREWALL_MODULE: for my $firewall_module_path (@firewall_module_paths) {
+		my $firewall_name = fileparse($firewall_module_path, qr/\.pm$/i);
+		my $firewall_perl_package = "VCL::Module::OS::Linux::firewall::$firewall_name";
+		
+		# Attempt to load the module
+		eval "use $firewall_perl_package";
+		if ($EVAL_ERROR) {
+			notify($ERRORS{'WARNING'}, 0, "$firewall_perl_package module could not be loaded, error:\n" . $EVAL_ERROR);
+			return;
+		}
+		notify($ERRORS{'DEBUG'}, 0, "$firewall_perl_package module loaded");
+		
+		# Attempt to create the object
+		my $firewall_object;
+		eval {
+			$firewall_object = ($firewall_perl_package)->new({data_structure => $self->data, base_package => ref($self)})
+		};
+		
+		if ($EVAL_ERROR) {
+			notify($ERRORS{'WARNING'}, 0, "failed to create $firewall_perl_package object, error: $EVAL_ERROR");
+		}
+		elsif (!$firewall_object) {
+			notify($ERRORS{'DEBUG'}, 0, "$firewall_perl_package object could not be initialized");
+		}
+		else {
+			my $address = sprintf('%x', $firewall_object);
+			notify($ERRORS{'DEBUG'}, 0, "$firewall_perl_package object created, address: $address");
+			$self->{firewall} = $firewall_object;
+			return $firewall_object;
+		}
+	}
+	
+	notify($ERRORS{'WARNING'}, 0, "failed to create firewall object to control $computer_node_name");
+	return;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 pre_capture
 
  Parameters  : none
@@ -3023,14 +3092,19 @@ sub service_exists {
 		}
 		
 		if (grep(/^$service_name$/, @service_names)) {
-			notify($ERRORS{'DEBUG'}, 0, "'$service_name' service exists on $computer_node_name, controlled by $init_module_name init module ($init_module_index)");
-			
 			$self->{service_init_module}{$service_name} = {
 				init_module_index => $init_module_index,
 				init_module_name => $init_module_name,
 			};
 			
-			return (wantarray) ? ($init_module_index) : 1;
+			if (wantarray) {
+				notify($ERRORS{'DEBUG'}, 0, "'$service_name' service exists on $computer_node_name, controlled by $init_module_name init module ($init_module_index), returning array: ($init_module_index)");
+				return $init_module_index;
+			}
+			else {
+				notify($ERRORS{'DEBUG'}, 0, "'$service_name' service exists on $computer_node_name, controlled by $init_module_name init module ($init_module_index), returning scalar: 1");
+				return 1;
+			}
 		}
 		else {
 			notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is not controlled by $init_module_name init module ($init_module_index)");
@@ -3208,12 +3282,10 @@ sub check_connection_on_port {
 		return;
 	}
 	
-	my $management_node_keys        = $self->data->get_management_node_keys();
 	my $computer_node_name          = $self->data->get_computer_node_name();
 	my $remote_ip                   = $self->data->get_reservation_remote_ip();
-	my $computer_public_ip_address  = $self->data->get_computer_public_ip_address();
+	my $computer_public_ip_address  = $self->get_public_ip_address();
 	my $request_state_name          = $self->data->get_request_state_name();
-	my $username                    = $self->data->get_user_login_id();
 	
 	my $port = shift;
 	if (!$port) {
@@ -3221,47 +3293,38 @@ sub check_connection_on_port {
 		return "failed";
 	}
 	
-	my $ret_val = "no";
-	my $command = "netstat -an";
-	my ($status, $output) = $self->execute($command, 0);
-	notify($ERRORS{'DEBUG'}, 0, "checking connections on node $computer_node_name on port $port");
-	foreach my $line (@{$output}) {
-		if ($line =~ /Connection refused|Permission denied/) {
-			chomp($line);
-			notify($ERRORS{'WARNING'}, 0, "$line");
-			if ($request_state_name =~ /reserved/) {
-				$ret_val = "failed";
-			}
-			else {
-				$ret_val = "timeout";
+	my $port_connection_info = $self->get_port_connection_info();
+	for my $protocol (keys %$port_connection_info) {
+		if (!defined($port_connection_info->{$protocol}{$port})) {
+			next;
+		}
+		
+		for my $connection (@{$port_connection_info->{$protocol}{$port}}) {
+			my $connection_local_ip = $connection->{local_ip};
+			my $connection_remote_ip = $connection->{remote_ip};
+			
+			if ($connection_local_ip ne $computer_public_ip_address) {
+				notify($ERRORS{'DEBUG'}, 0, "ignoring connection, not connected to public IP address ($computer_public_ip_address): $connection_remote_ip --> $connection_local_ip:$port ($protocol)");
+				next;
 			}
-			return $ret_val;
-		} ## end if ($line =~ /Connection refused|Permission denied/)
-		if ($line =~ /tcp\s+([0-9]*)\s+([0-9]*)\s($computer_public_ip_address:$port)\s+([.0-9]*):([0-9]*)(.*)(ESTABLISHED)/) {
-			if ($4 eq $remote_ip) {
-				$ret_val = "connected";
-				return $ret_val;
+			
+			if ($connection_remote_ip eq $remote_ip) {
+				notify($ERRORS{'DEBUG'}, 0, "connection detected from reservation remote IP: $connection_remote_ip --> $connection_local_ip:$port ($protocol)");
+				return 1;
 			}
-			else {
-				my $new_remote_ip = $4;
-				# this isn't the defined remoteIP
-				# Confirm the user is logged in
-				# Is user logged in
-				if (!$self->user_logged_in()) {
-					notify($ERRORS{'OK'}, 0, "Detected $new_remote_ip is connected. $username is not logged in yet. Returning no connection");
-					$ret_val = "no";
-					return $ret_val;
-				}
-				else {
-					$self->data->set_reservation_remote_ip($new_remote_ip);
-					notify($ERRORS{'OK'}, 0, "Updating reservation remote_ip with $new_remote_ip");
-					$ret_val = "conn_wrong_ip";
-					return $ret_val;
-				}
+			
+			# Connection is not from reservation remote IP address, check if user is logged in
+			if ($self->user_logged_in()) {
+				notify($ERRORS{'DEBUG'}, 0, "connection detected from different remote IP address than current reservation remote IP ($remote_ip): $connection_remote_ip --> $connection_local_ip:$port ($protocol), updating reservation remote IP to $connection_remote_ip");
+				$self->data->set_reservation_remote_ip($connection_remote_ip);
+				return 1;
 			}
-		}    # tcp check
+			
+			notify($ERRORS{'DEBUG'}, 0, "ignoring connection, user is not logged in and remote IP address does not match current reservation remote IP ($remote_ip): $connection_remote_ip --> $connection_local_ip:$port ($protocol)");
+		}
 	}
-	return $ret_val;
+	
+	return 0;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -5135,6 +5198,42 @@ sub get_port_connection_info {
 	return $connection_info;
 }
 
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 enable_ip_forwarding
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Enables IP forwarding by executing:
+               echo 1 > /proc/sys/net/ipv4/ip_forward
+
+=cut
+
+sub enable_ip_forwarding {
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my $command = "echo 1 > /proc/sys/net/ipv4/ip_forward";
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command to enable IP forwarding on $computer_node_name: $command");
+		return;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to enable IP forwarding on $computer_node_name, command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "verified IP forwarding is enabled on $computer_node_name");
+		return 1;
+	}
+}
+
 ##/////////////////////////////////////////////////////////////////////////////
 1;
 __END__

Added: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm?rev=1644185&view=auto
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm (added)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm Tue Dec  9 21:11:37 2014
@@ -0,0 +1,882 @@
+#!/usr/bin/perl -w
+###############################################################################
+# $Id:  $
+###############################################################################
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###############################################################################
+
+=head1 NAME
+
+VCL::Module::OS::Linux::firewall::iptables.pm
+
+=head1 DESCRIPTION
+
+ This module provides VCL support for iptables-based firewalls.
+
+=cut
+
+##############################################################################
+package VCL::Module::OS::Linux::firewall::iptables;
+
+# Specify the lib path using FindBin
+use FindBin;
+use lib "$FindBin::Bin/../../../../..";
+
+# Configure inheritance
+use base qw(VCL::Module::OS::Linux);
+
+# Specify the version of this module
+our $VERSION = '2.3';
+
+our @ISA;
+
+# Specify the version of Perl to use
+use 5.008000;
+
+use strict;
+use warnings;
+use diagnostics;
+
+use VCL::utils;
+
+##############################################################################
+
+=head1 OBJECT METHODS
+
+=cut
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 initialize
+
+ Parameters  : none
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub initialize {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my $arguments = shift || {};
+	
+	# Check if base_package argument was specified
+	# This is necessary for ManagementNode OS objects to work
+	# Otherwise the base Linux.pm subroutines would be used instead of ManagementNode.pm
+	if (defined($arguments->{base_package})) {
+		notify($ERRORS{'DEBUG'}, 0, "overriding object package: " . $ISA[0] . " --> $arguments->{base_package}");
+		@ISA = ($arguments->{base_package});
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	notify($ERRORS{'DEBUG'}, 0, "initializing " . ref($self) . " object to control $computer_name");
+	
+	if (!$self->service_exists('iptables')) {
+		notify($ERRORS{'DEBUG'}, 0, ref($self) . " object not initialized to control $computer_name, iptables service does not exist");
+		return 0;
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, ref($self) . " object initialized to control $computer_name");
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 insert_rule
+
+ Parameters  : none
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub insert_rule {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my $arguments = shift;
+	if (!$arguments) {
+		notify($ERRORS{'WARNING'}, 0, "argument was not supplied");
+		return;
+	}
+	elsif (!ref($arguments) || ref($arguments) ne 'HASH') {
+		notify($ERRORS{'WARNING'}, 0, "argument is not a hash reference");
+		return;
+	}
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $command = '/sbin/iptables';
+	
+	# Add the table argument if specified
+	if ($arguments->{table}) {
+		$command .= " -t $arguments->{table}";
+	}
+	
+	# Get the chain argument
+	my $chain = $arguments->{chain};
+	if (!defined($chain)) {
+		notify($ERRORS{'WARNING'}, 0, "chain argument was not specified:\n" . format_data($arguments));
+		return;
+	}
+	$command .= " -I $chain";
+	
+	# Add the parameters to the command
+	for my $parameter (sort keys %{$arguments->{parameters}}) {
+		my $value = $arguments->{parameters}{$parameter};
+		$command .= " --$parameter $value";
+	}
+	
+	# Add the match extension to the command
+	for my $match_extension (sort keys %{$arguments->{match_extensions}}) {
+		$command .= " --match $match_extension";
+		for my $option (sort keys %{$arguments->{match_extensions}{$match_extension}}) {
+			my $value = $arguments->{match_extensions}{$match_extension}{$option};
+			
+			if ($option =~ /(comment)/) {
+				$value = "\"$value\"";
+			}
+			
+			$command .= " --$option $value";
+		}
+	}
+	
+	# Add the target extensions to the command
+	for my $target_extension (sort keys %{$arguments->{target_extensions}}) {
+		$command .= " --jump $target_extension";
+		for my $option (sort keys %{$arguments->{target_extensions}{$target_extension}}) {
+			my $value = $arguments->{target_extensions}{$target_extension}{$option};
+			$command .= " --$option $value";
+		}
+	}
+	
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command $computer_name: $command");
+		return;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to add iptables rule on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "added iptables rule on $computer_name, command: $command");
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_rule
+
+ Parameters  : hash reference
+               -or-
+					$table_name, $chain_name, $rule_specification
+ Returns     : boolean
+ Description : Deletes a rule.
+
+=cut
+
+sub delete_rule {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my $argument = shift;
+	if (!$argument) {
+		notify($ERRORS{'WARNING'}, 0, "argument was not supplied");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $command = '/sbin/iptables';
+	
+	
+	if (ref($argument) && ref($argument) eq 'HASH') {
+		# Add the table argument if specified
+		if ($argument->{table}) {
+			$command .= " -t $argument->{table}";
+		}
+		
+		# Get the chain argument
+		my $chain = $argument->{chain};
+		if (!defined($chain)) {
+			notify($ERRORS{'WARNING'}, 0, "chain argument was not specified:\n" . format_data($argument));
+			return;
+		}
+		$command .= " -D $chain";
+		
+		# Add the parameters to the command
+		for my $parameter (sort keys %{$argument->{parameters}}) {
+			my $value = $argument->{parameters}{$parameter};
+			$command .= " --$parameter $value";
+		}
+		
+		# Add the match extension to the command
+		for my $match_extension (sort keys %{$argument->{match_extensions}}) {
+			$command .= " --match $match_extension";
+			for my $option (sort keys %{$argument->{match_extensions}{$match_extension}}) {
+				my $value = $argument->{match_extensions}{$match_extension}{$option};
+				
+				if ($option =~ /(comment)/) {
+					$value = "\"$value\"";
+				}
+				
+				$command .= " --$option $value";
+			}
+		}
+		
+		# Add the target extensions to the command
+		for my $target_extension (sort keys %{$argument->{target_extensions}}) {
+			$command .= " --jump $target_extension";
+			for my $option (sort keys %{$argument->{target_extensions}{$target_extension}}) {
+				my $value = $argument->{target_extensions}{$target_extension}{$option};
+				$command .= " --$option $value";
+			}
+		}
+	}
+	elsif (my $type = ref($argument)) {
+		notify($ERRORS{'WARNING'}, 0, "argument $type reference not supported, argument must only be a HASH reference or scalar");
+		return;
+	}
+	else {
+		my $table_name = $argument;
+		my ($chain_name, $specification) = @_;
+		if (!defined($chain_name) || !defined($specification)) {
+			notify($ERRORS{'WARNING'}, 0, "1st argument is a scalar, 2nd chain name and 3rd rule specification arguments not provided");
+			return;
+		}
+		$command .= " -D $chain_name -t $table_name $specification";
+	}
+	
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command $computer_name: $command");
+		return;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to delete iptables rule on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "deleted iptables rule on $computer_name, command: $command");
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 create_chain
+
+ Parameters  : $table_name, $chain_name
+ Returns     : boolean
+ Description : Creates a new chain. Returns true if the chain was successfully
+               created or already exists.
+
+=cut
+
+sub create_chain {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my ($table_name, $chain_name) = @_;
+	if (!defined($table_name)) {
+		notify($ERRORS{'WARNING'}, 0, "table name argument was not specified");
+		return;
+	}
+	elsif (!defined($chain_name)) {
+		notify($ERRORS{'WARNING'}, 0, "chain name argument was not specified");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $command = "/sbin/iptables --new-chain $chain_name --table $table_name";
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command $computer_name: $command");
+		return;
+	}
+	elsif (grep(/already exists/i, @$output)) {
+		notify($ERRORS{'OK'}, 0, "'$chain_name' chain in '$table_name' table already exists on $computer_name");
+		return 1;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to create '$chain_name' chain in '$table_name' table on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "created '$chain_name' chain in '$table_name' table on $computer_name");
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_chain
+
+ Parameters  : $table_name, $chain_name
+ Returns     : boolean
+ Description : Deletes the specified chain from the specified table. All rules
+               which exist in the chain or reference the chain are deleted prior
+               to deletion of the chain.
+
+=cut
+
+sub delete_chain {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my ($table_name, $chain_name) = @_;
+	if (!defined($table_name)) {
+		notify($ERRORS{'WARNING'}, 0, "table name argument was not specified");
+		return;
+	}
+	elsif (!defined($chain_name)) {
+		notify($ERRORS{'WARNING'}, 0, "chain name argument was not specified");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $table_info = $self->get_table_info($table_name);
+	if (!defined($table_info->{$chain_name})) {
+		notify($ERRORS{'DEBUG'}, 0, "'$chain_name' chain in '$table_name' table does not exist on $computer_name");
+		return 1;
+	}
+	
+	# Flush the chain first - delete will fail if the chain still contains rules
+	if (!$self->flush_chain($table_name, $chain_name)) {
+		notify($ERRORS{'WARNING'}, 0, "unable to delete '$chain_name' chain from '$table_name' table on $computer_name, failed to flush chain prior to deletion");
+		return;
+	}
+	
+	# Delete all rules which reference the chain being deleted or else the chain can't be deleted
+	if (!$self->delete_chain_references($table_name, $chain_name)) {
+		notify($ERRORS{'WARNING'}, 0, "unable to delete '$chain_name' chain from '$table_name' table on $computer_name, failed to delete all rules which reference the chain prior to deletion");
+		return;
+	}
+	
+	my $command = "/sbin/iptables --delete-chain $chain_name --table $table_name";
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command $computer_name: $command");
+		return;
+	}
+	elsif (grep(/Too many links/i, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "unable to delete '$chain_name' chain from '$table_name' table on $computer_name, the chain is referenced by another rule");
+		return 0;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to delete '$chain_name' chain from '$table_name' table on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "deleted '$chain_name' chain from '$table_name' table on $computer_name");
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_chain_references
+
+ Parameters  : $table_name, $referenced_chain_name
+ Returns     : boolean
+ Description : Checks all chains in the specified table for references to the
+               $referenced_chain_name argument. If found, the referencing rules
+               are deleted.
+
+=cut
+
+sub delete_chain_references {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my ($table_name, $referenced_chain_name) = @_;
+	if (!defined($table_name)) {
+		notify($ERRORS{'WARNING'}, 0, "table name argument was not specified");
+		return;
+	}
+	elsif (!defined($referenced_chain_name)) {
+		notify($ERRORS{'WARNING'}, 0, "referenced chain name argument was not specified");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $table_info = $self->get_table_info($table_name);
+	for my $referencing_chain_name (keys %$table_info) {
+		for my $rule_specification (@{$table_info->{$referencing_chain_name}{rules}}) {
+			if ($rule_specification =~ /-j $referenced_chain_name(\s|$)/) {
+				notify($ERRORS{'DEBUG'}, 0, "rule in '$table_name' table references '$referenced_chain_name' chain, referencing chain: $referencing_chain_name, rule specification: $rule_specification");
+				if (!$self->delete_rule($table_name, $referencing_chain_name, $rule_specification)) {
+					return;
+				}
+			}
+		}
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "deleted all rules in '$table_name' table referencing '$referenced_chain_name' chain on $computer_name");
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 flush_chain
+
+ Parameters  : $table_name, $chain_name
+ Returns     : boolean
+ Description : Flushes (deletes) rules from the specified chain.
+
+=cut
+
+sub flush_chain {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my ($table_name, $chain_name) = @_;
+	if (!defined($table_name)) {
+		notify($ERRORS{'WARNING'}, 0, "table name argument was not specified");
+		return;
+	}
+	elsif (!defined($chain_name)) {
+		notify($ERRORS{'WARNING'}, 0, "chain name argument was not specified");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $command = "/sbin/iptables --flush";
+	my $chain_text = 'all chains';
+	if ($chain_name ne '*') {
+		$chain_text = "'$chain_name' chain";
+		$command .= " $chain_name";
+	}
+	$command .= " --table $table_name";
+	
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command $computer_name: $command");
+		return;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to flush $chain_text in '$table_name' table on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "flushed $chain_text in '$table_name' table on $computer_name");
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_table_info
+
+ Parameters  : $table_name, $chain_name (optional)
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub get_table_info {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my ($table_name, $chain_name) = @_;
+	if (!defined($table_name)) {
+		notify($ERRORS{'WARNING'}, 0, "table name argument was not specified");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $command = "/sbin/iptables --list-rules";
+	my $chain_text = '';
+	if (defined($chain_name)) {
+		$command .= " $chain_name";
+		$chain_text = "of '$chain_name' chain ";
+	}
+	$command .= " --table $table_name";
+	
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command $computer_name: $command");
+		return;
+	}
+	elsif ($exit_status ne '0') {
+		notify($ERRORS{'WARNING'}, 0, "failed to list rules " . $chain_text . "from '$table_name' table on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	
+	my $table_info = {};
+	for my $line (@$output) {
+		my ($iptables_command, $chain_name, $specification) = $line =~ /^(-\w) ([^ ]+)\s*(.*)$/;
+		if (!defined($iptables_command) || !defined($chain_name)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to parse line: '$line'\ncommand: $command");
+			next;
+		}
+		$specification = '' unless defined($specification);
+		
+		if ($iptables_command eq '-P') {
+			# -P, --policy chain target (Set  the policy for the chain to the given target)
+			$table_info->{$chain_name}{policy} = $specification;
+		}
+		elsif ($iptables_command eq '-N') {
+			# -N, --new-chain chain
+			$table_info->{$chain_name} = {} unless defined($table_info->{$chain_name});
+		}
+		elsif ($iptables_command eq '-A') {
+			# -A, --append chain rule-specification
+			push @{$table_info->{$chain_name}{rules}}, $specification;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "'$iptables_command' command is not supported: $line");
+		}
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "retrieved rules " . $chain_text . "from '$table_name' table from $computer_name:\n" . format_data($table_info));
+	return $table_info;
+}
+
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 configure_nat
+
+ Parameters  : none
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub configure_nat {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+	
+	my $reservation_id = $self->data->get_reservation_id();
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my $table_info = $self->get_table_info('nat');
+	if (!$table_info) {
+		notify($ERRORS{'WARNING'}, 0, "failed to configure NAT on $computer_name, nat table info could not be retrieved");
+		return;
+	}
+	elsif (!defined($table_info->{PREROUTING})) {
+		notify($ERRORS{'WARNING'}, 0, "unable to configure NAT on $computer_name, nat table does not contain a PREROUTING chain:\n" . format_data($table_info));
+		return;
+	}
+	elsif (!defined($table_info->{POSTROUTING})) {
+		notify($ERRORS{'WARNING'}, 0, "unable to configure NAT on $computer_name, nat table does not contain a POSTROUTING chain:\n" . format_data($table_info));
+		return;
+	}
+	
+	# Check if NAT has previously been configured
+	my $nat_previously_configured = 0;
+	for my $rule_specification (@{$table_info->{POSTROUTING}{rules}}) {
+		if ($rule_specification =~ /MASQUERADE/) {
+			$nat_previously_configured = 1;
+			notify($ERRORS{'DEBUG'}, 0, "POSTROUTING chain in nat table contains a MASQUERADE rule, assuming NAT has already been configured: $rule_specification");
+			last;
+		}
+	}
+	if (!$nat_previously_configured) {
+		my $private_interface_name = $self->get_private_interface_name();
+		my $private_ip_address = $self->get_private_ip_address();
+		my $public_interface_name = $self->get_public_interface_name();
+		my $public_ip_address = $self->get_public_ip_address();
+		
+		my $natport_ranges_variable = get_variable('natport_ranges') || '49152-65535';
+		my $destination_ports = '';
+		for my $natport_range (split(/[,;]+/, $natport_ranges_variable)) {
+			my ($start_port, $end_port) = $natport_range =~ /(\d+)-(\d+)/g;
+			if (!defined($start_port)) {
+				notify($ERRORS{'WARNING'}, 0, "unable to parse NAT port range: '$natport_range'");
+				next;
+			}
+			$destination_ports .= "," if ($destination_ports);
+			$destination_ports .= "$start_port:$end_port";
+		}
+		
+		
+		if (!$self->insert_rule({
+			'table' => 'nat',
+			'chain' => 'POSTROUTING',
+			'parameters' => {
+				'out-interface' => $private_interface_name,
+				'jump' => 'MASQUERADE',
+			},
+			'match_extensions' => {
+				'comment' => {
+					'comment' => "change IP of outbound private $private_interface_name packets to NAT host private IP address $private_interface_name",
+				},
+			},
+		})) {
+			return;
+		}
+		
+		if (!$self->insert_rule({
+			'chain' => 'INPUT',
+			'parameters' => {
+				'in-interface' => $public_interface_name,
+				'destination' => $public_ip_address,
+				'jump' => 'ACCEPT',
+				'protocol' => 'tcp',
+			},
+			'match_extensions' => {
+				'state' => {
+					'state' => 'RELATED,ESTABLISHED',
+				},
+				'multiport' => {
+					'destination-ports' => $destination_ports,
+				},
+			},
+		})) {
+			return;
+		}
+		
+		if (!$self->insert_rule({
+			'chain' => 'INPUT',
+			'parameters' => {
+				'in-interface' => $public_interface_name,
+				'destination' => $public_ip_address,
+				'jump' => 'ACCEPT',
+				'protocol' => 'udp',
+			},
+			'match_extensions' => {
+				'state' => {
+					'state' => 'RELATED,ESTABLISHED',
+				},
+				'multiport' => {
+					'destination-ports' => $destination_ports,
+				},
+			},
+		})) {
+			return;
+		}
+		
+		if (!$self->insert_rule({
+			'chain' => 'FORWARD',
+			'parameters' => {
+				'in-interface' => $public_interface_name,
+				'out-interface' => $private_interface_name,
+				'jump' => 'ACCEPT',
+			},
+			'match_extensions' => {
+				'state' => {
+					'state' => 'NEW,RELATED,ESTABLISHED',
+				},
+				'comment' => {
+					'comment' => "forward inbound packets from public $public_interface_name to private $private_interface_name",
+				},
+			},	
+		})) {
+			return;
+		}
+		
+		if (!$self->insert_rule({
+			'chain' => 'FORWARD',
+			'parameters' => {
+				'in-interface' => $private_interface_name,
+				'out-interface' => $public_interface_name,
+				'jump' => 'ACCEPT',
+			},
+			'match_extensions' => {
+				'comment' => {
+					'comment' => "forward outbound packets from private $private_interface_name to public $public_interface_name",
+				},
+			},
+		})) {
+			return;
+		}
+		
+		#if (!$self->insert_rule({
+		#	'chain' => 'INPUT',
+		#	'parameters' => {
+		#		'in-interface' => $public_interface_name,
+		#	},
+		#	'target_extensions' => {
+		#		'REJECT' => {
+		#			'reject-with' => "icmp-host-prohibited",
+		#		},
+		#	},
+		#})) {
+		#	return;
+		#}
+		#
+		#if (!$self->insert_rule({
+		#	'chain' => 'FORWARD',
+		#	'target_extensions' => {
+		#		'REJECT' => {
+		#			'reject-with' => "icmp-host-prohibited",
+		#		},
+		#	},
+		#})) {
+		#	return;
+		#}
+	}
+	
+	# Check if chain for reservation has already been created
+	if (defined($table_info->{$reservation_id})) {
+		notify($ERRORS{'DEBUG'}, 0, "'$reservation_id' chain already exists in nat table on $computer_name for this reservation");
+	}
+	else {
+		if (!$self->create_chain('nat', $reservation_id)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to configure NAT on $computer_name, '$reservation_id' chain could not be created in nat table for this reservation");
+			return;
+		}
+	}
+	
+	# Check if rule to jump to reservation's chain already exists in the PREROUTING table
+	my $jump_previously_configured = 0;
+	for my $rule_specification (@{$table_info->{PREROUTING}{rules}}) {
+		if ($rule_specification =~ /-j $reservation_id(\s|$)/) {
+			$jump_previously_configured = 1;
+			notify($ERRORS{'DEBUG'}, 0, "PREROUTING chain in nat table on $computer_name already contains a rule to jump to '$reservation_id' chain: $rule_specification");
+			last;
+		}
+	}
+	if (!$jump_previously_configured) {
+		if (!$self->insert_rule({
+			'table' => 'nat',
+			'chain' => 'PREROUTING',
+			'parameters' => {
+				'jump' => $reservation_id,
+			},
+		})) {
+			notify($ERRORS{'WARNING'}, 0, "unable to configure NAT on $computer_name, failed to create rule in PREROUTING chain in nat table to jump to '$reservation_id' chain");
+			return;
+		}
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "successfully configured NAT on $computer_name for reservation");
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 add_nat_port_forward
+
+ Parameters  : $protocol, $source_port, $destination_ip_address, $destination_port, $chain_name (optional)
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub add_nat_port_forward {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return 0;
+	}
+
+	my $reservation_id = $self->data->get_reservation_id();
+	my $computer_name = $self->data->get_computer_hostname();
+	
+	my ($protocol, $source_port, $destination_ip_address, $destination_port, $chain_name) = @_;
+	if (!defined($protocol)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol argument was not provided");
+		return;
+	}
+	elsif (!defined($source_port)) {
+		notify($ERRORS{'WARNING'}, 0, "source port argument was not provided");
+		return;
+	}
+	elsif (!defined($destination_ip_address)) {
+		notify($ERRORS{'WARNING'}, 0, "destination IP address argument was not provided");
+		return;
+	}
+	elsif (!defined($destination_port)) {
+		notify($ERRORS{'WARNING'}, 0, "destination port argument was not provided");
+		return;
+	}
+	$chain_name = 'PREROUTING' unless defined $chain_name;
+	
+	$protocol = lc($protocol);
+	
+	my $public_interface_name = $self->get_public_interface_name();
+	my $public_ip_address = $self->get_public_ip_address();
+	
+	if ($self->insert_rule({
+		'table' => 'nat',
+		'chain' => $chain_name,
+		'parameters' => {
+			'protocol' => $protocol,
+			'in-interface' => $public_interface_name,
+			'destination' => $public_ip_address,
+		},
+		'match_extensions' => {
+			'comment' => {
+				'comment' => "change destination address: $public_ip_address:$source_port --> $destination_ip_address:$destination_port ($protocol)",
+			},
+			$protocol => {
+				'destination-port' => $source_port,
+			},
+		},
+		'target_extensions' => {
+			'DNAT' => {
+				'to-destination' => "$destination_ip_address:$destination_port",
+			},
+		},
+	})) {
+		notify($ERRORS{'OK'}, 0, "added NAT port forward on $computer_name: $public_interface_name:$source_port --> $destination_ip_address:$destination_port");
+		return 1;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to add NAT port forward on $computer_name: $public_interface_name:$source_port --> $destination_ip_address:$destination_port");
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+1;
+__END__
+
+=head1 SEE ALSO
+
+L<http://cwiki.apache.org/VCL/>
+
+=cut

Added: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init.pm?rev=1644185&view=auto
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init.pm (added)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init.pm Tue Dec  9 21:11:37 2014
@@ -0,0 +1,100 @@
+#!/usr/bin/perl -w
+###############################################################################
+# $Id: $
+###############################################################################
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###############################################################################
+
+=head1 NAME
+
+VCL::Module::OS::Linux::init
+
+=head1 DESCRIPTION
+
+ This module is the parent class for the Linux init modules.
+
+=cut
+
+##############################################################################
+package VCL::Module::OS::Linux::init;
+
+# Specify the lib path using FindBin
+use FindBin;
+use lib "$FindBin::Bin/../../../..";
+
+# Configure inheritance
+use base qw(VCL::Module::OS::Linux);
+
+# Specify the version of this module
+our $VERSION = '2.3';
+
+our @ISA;
+
+# Specify the version of Perl to use
+use 5.008000;
+
+use strict;
+use warnings;
+use diagnostics;
+
+use VCL::utils;
+
+##############################################################################
+
+=head1 OBJECT METHODS
+
+=cut
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 initialize
+
+ Parameters  : 
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub initialize {
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $arguments = shift || {};
+	
+	# Check if base_package argument was specified
+	# This is necessary for ManagementNode OS objects to work
+	# Otherwise the base Linux.pm subroutines would be used instead of ManagementNode.pm
+	if (defined($arguments->{base_package})) {
+		notify($ERRORS{'DEBUG'}, 0, "overriding object package: " . $ISA[0] . " --> $arguments->{base_package}");
+		@ISA = ($arguments->{base_package});
+	}
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+1;
+__END__
+
+=head1 SEE ALSO
+
+L<http://cwiki.apache.org/VCL/>
+
+=cut

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/SysV.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/SysV.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/SysV.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/SysV.pm Tue Dec  9 21:11:37 2014
@@ -39,7 +39,7 @@ use FindBin;
 use lib "$FindBin::Bin/../../../../..";
 
 # Configure inheritance
-use base qw(VCL::Module::OS::Linux);
+use base qw(VCL::Module::OS::Linux::init);
 
 # Specify the version of this module
 our $VERSION = '2.3';

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm Tue Dec  9 21:11:37 2014
@@ -38,7 +38,7 @@ use FindBin;
 use lib "$FindBin::Bin/../../../../..";
 
 # Configure inheritance
-use base qw(VCL::Module::OS::Linux);
+use base qw(VCL::Module::OS::Linux::init);
 
 # Specify the version of this module
 our $VERSION = '2.3';

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/systemd.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/systemd.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/systemd.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/systemd.pm Tue Dec  9 21:11:37 2014
@@ -39,7 +39,7 @@ use FindBin;
 use lib "$FindBin::Bin/../../../../..";
 
 # Configure inheritance
-use base qw(VCL::Module::OS::Linux);
+use base qw(VCL::Module::OS::Linux::init);
 
 # Specify the version of this module
 our $VERSION = '2.3';

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm Tue Dec  9 21:11:37 2014
@@ -10818,8 +10818,11 @@ sub get_environment_variable_value {
 =head2 check_connection_on_port
 
  Parameters  : $port
- Returns     : (connected|conn_wrong_ip|timeout|failed)
- Description : uses netstat to see if any thing is connected to the provided port
+ Returns     : boolean
+ Description : Checks if a connection is established to the port specified from
+					the reservation remote IP address. If a connection is detected
+					from another address and the user is logged in,
+					reservation.remoteIP is updated.
 
 =cut
 
@@ -10832,7 +10835,7 @@ sub check_connection_on_port {
 	
 	my $computer_node_name          = $self->data->get_computer_node_name();
 	my $remote_ip                   = $self->data->get_reservation_remote_ip();
-	my $computer_public_ip_address  = $self->data->get_computer_public_ip_address();
+	my $computer_public_ip_address  = $self->get_public_ip_address();
 	my $request_state_name          = $self->data->get_request_state_name();
 	
 	my $port = shift;
@@ -10841,104 +10844,38 @@ sub check_connection_on_port {
 		return "failed";
 	}
 	
-	my $ret_val = "no";
-	my $command = "netstat -an";
-	my ($status, $output) = $self->execute($command, 0, 30, 0);
-	
-	notify($ERRORS{'DEBUG'}, 0, "checking connections on node $computer_node_name on port $port");
-	
-	foreach my $line (@{$output}) {
-		if ($line =~ /Connection refused|Permission denied/) {
-			chomp($line);
-			notify($ERRORS{'WARNING'}, 0, "$line");
-			if ($request_state_name =~ /reserved/) {
-				$ret_val = "failed";
-			}
-			else {
-				$ret_val = "timeout";
-			}
-			return $ret_val;
-		} ## end if ($line =~ /Connection refused|Permission denied/)
+	my $port_connection_info = $self->get_port_connection_info();
+	for my $protocol (keys %$port_connection_info) {
+		if (!defined($port_connection_info->{$protocol}{$port})) {
+			next;
+		}
 		
-		if ($line =~ /\s+($computer_public_ip_address:$port)\s+([.0-9]*):([0-9]*)\s+(ESTABLISHED)/) {
-			if ($2 eq $remote_ip) {
-				$ret_val = "connected";
-				return $ret_val;
+		for my $connection (@{$port_connection_info->{$protocol}{$port}}) {
+			my $connection_local_ip = $connection->{local_ip};
+			my $connection_remote_ip = $connection->{remote_ip};
+			
+			if ($connection_local_ip ne $computer_public_ip_address) {
+				notify($ERRORS{'DEBUG'}, 0, "ignoring connection, not connected to public IP address ($computer_public_ip_address): $connection_remote_ip --> $connection_local_ip:$port ($protocol)");
+				next;
 			}
-			else {
-				# this isn't the remoteIP
-				# Is user logged in
-				if (!$self->user_logged_in()) {
-					notify($ERRORS{'OK'}, 0, "Detected $4 is connected. user is not logged in yet. Returning no connection");
-					$ret_val = "no";
-					return $ret_val;
-				}
-				else {
-					my $new_remote_ip = $2;
-					$self->data->set_reservation_remote_ip($new_remote_ip);  
-					notify($ERRORS{'OK'}, 0, "Updating reservation remote_ip with $new_remote_ip");
-					$ret_val = "conn_wrong_ip";
-					return $ret_val;
-				}
+			
+			if ($connection_remote_ip eq $remote_ip) {
+				notify($ERRORS{'DEBUG'}, 0, "connection detected from reservation remote IP: $connection_remote_ip --> $connection_local_ip:$port ($protocol)");
+				return 1;
 			}
+			
+			# Connection is not from reservation remote IP address, check if user is logged in
+			if ($self->user_logged_in()) {
+				notify($ERRORS{'DEBUG'}, 0, "connection detected from different remote IP address than current reservation remote IP ($remote_ip): $connection_remote_ip --> $connection_local_ip:$port ($protocol), updating reservation remote IP to $connection_remote_ip");
+				$self->data->set_reservation_remote_ip($connection_remote_ip);
+				return 1;
+			}
+			
+			notify($ERRORS{'DEBUG'}, 0, "ignoring connection, user is not logged in and remote IP address does not match current reservation remote IP ($remote_ip): $connection_remote_ip --> $connection_local_ip:$port ($protocol)");
 		}
 	}
 	
-	
-	return $ret_val;
-}
-
-#/////////////////////////////////////////////////////////////////////////////
-
-=head2 firewall_compare_update
-
- Parameters  : $node,$reote_IP, $identity, $type
- Returns     : 0 or 1 (nochange or updated)
- Description : compares and updates the firewall for rdp port, specfically for windows
-               Currently only handles windows and allows two seperate scopes
-
-=cut
-
-sub firewall_compare_update {
-   my $self = shift;
-   if (ref($self) !~ /windows/i) {
-      notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
-      return;
-   }
-	
-   my $computer_node_name = $self->data->get_computer_node_name();
-   my $imagerevision_id   = $self->data->get_imagerevision_id();
-   my $remote_ip          = $self->data->get_reservation_remote_ip();
-	
-	if (!$remote_ip) {
-		notify($ERRORS{'WARNING'}, 0, "unable to update firewall on $computer_node_name, remote IP could not be retrieved for reservation");
-      return;
-	}
-	
-   # Retrieve the connect method info
-   my $connect_method_info = get_connect_method_info($imagerevision_id);
-   if (!$connect_method_info) {
-      notify($ERRORS{'WARNING'}, 0, "failed to retrieve connect method info for image revision $imagerevision_id");
-      return;
-   }
-	
-   # Retrieve the firewall configuration from the computer
-   my $firewall_configuration = $self->get_firewall_configuration() || return;
-	
-	# Loop through the connect methods, check to make sure firewall is open for remote IP
-   for my $connect_method_id (sort keys %{$connect_method_info} ) {
-      my $connect_method_name = $connect_method_info->{$connect_method_id}{name};
-      my $protocol            = $connect_method_info->{$connect_method_id}{protocol} || 'TCP';
-      my $port                = $connect_method_info->{$connect_method_id}{port};
-		
-		next if (!$port);
-		
-		if ($self->enable_firewall_port($protocol, $port, $remote_ip, 0)) {
-			notify($ERRORS{'DEBUG'}, 0, "opened/verified firewall port $port on $computer_node_name for $remote_ip $connect_method_name connect method");
-		}
-	}
-	return 1;
-
+	return 0;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -11745,16 +11682,25 @@ sub check_rdp_port_configuration {
 		return;
 	}
 	
-	# Make sure only 1 port is defined
-	my @protocols = keys %{$connect_method_port_info};
-	my $protocol = $connect_method_port_info->{$protocols[0]};
-	my @ports = keys %$protocol;
-	my $connect_method_rdp_port = $ports[0];
-	if (scalar(@protocols) > 1 || scalar(@ports) > 1) {
+	# Extract the port numbers - multiple ports may be defined, for example TCP/3389 and UDP/3389
+	my %connect_method_port_hash;
+	for my $connect_method_port_id (keys %$connect_method_port_info) {
+		my $port = $connect_method_port_info->{$connect_method_port_id}{port};
+		$connect_method_port_hash{$port} = 1;
+	}
+	
+	# Make sure a single port number is defined for the RDP connect method
+	my @connect_method_ports = keys(%connect_method_port_hash);
+	if (!@connect_method_ports) {
+		notify($ERRORS{'WARNING'}, 0, "port is not defined for connect method:\n" . format_data($connect_method_port_info));
+		return;
+	}
+	elsif (scalar(@connect_method_ports) > 1) {
 		notify($ERRORS{'WARNING'}, 0, "unable to determine which port is supposed to be used for RDP, multiple ports are defined for connect method:\n" . format_data($connect_method_port_info));
 		return;
 	}
 	
+	my $connect_method_rdp_port = $connect_method_ports[0];
 	my $rdp_port_key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp';
 	my $rdp_port_value = 'PortNumber';
 	

Modified: vcl/trunk/managementnode/lib/VCL/Module/State.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/State.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/State.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/State.pm Tue Dec  9 21:11:37 2014
@@ -93,6 +93,7 @@ sub initialize {
 	my $is_vm = $self->data->get_computer_vmhost_id(0);
 	my $is_parent_reservation = $self->data->is_parent_reservation();
 	my $reservation_count = $self->data->get_reservation_count();
+	my $nathost_id = $self->data->get_nathost_id(0);
 	
 	# Initialize the database handle count
 	$ENV{dbh_count} = 0;
@@ -138,6 +139,20 @@ sub initialize {
 		$self->set_vmhost_os($vmhost_os);
 	}
 	
+	# Create a NAT host OS object if computer is mapped to a NAT host
+	my $nathost_os;
+	if ($nathost_id) {
+		$nathost_os = $self->create_nathost_os_object();
+		if (!$nathost_os) {
+			$self->reservation_failed("failed to create NAT host OS object");
+		}
+		$self->set_nathost_os($nathost_os);
+		
+		# Allow the OS object to access the nathost_os object
+		# This is necessary to allow the OS code to call the subroutines to forward ports
+		$self->os->set_nathost_os($self->nathost_os());
+	}
+	
 	# Create a provisioning object
 	if (my $provisioner = $self->create_provisioning_object()) {
 		$self->set_provisioner($provisioner);