sub nat_configure_host()

in managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm [1918:2173]


sub nat_configure_host {
	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 $computer_name = $self->data->get_computer_hostname();
	my $public_ip_address = $self->data->get_nathost_public_ip_address();
	my $internal_ip_address = $self->data->get_nathost_internal_ip_address();
	
	my $nat_host_chain_name = $self->get_nat_host_chain_name();
	
	# Enable IP port forwarding
	if (!$self->os->enable_ip_forwarding()) {
		notify($ERRORS{'WARNING'}, 0, "unable to configure NAT host $computer_name, failed to enable IP forwarding");
		return;
	}
	
	my $nat_table_info = $self->get_table_info('nat');
	if (!$nat_table_info) {
		notify($ERRORS{'WARNING'}, 0, "failed to configure NAT on $computer_name, nat table info could not be retrieved");
		return;
	}
	elsif (!defined($nat_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($nat_table_info));
		return;
	}
	elsif (!defined($nat_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($nat_table_info));
		return;
	}
	
	# Check if NAT has previously been configured
	if (defined($nat_table_info->{$nat_host_chain_name})) {
		notify($ERRORS{'DEBUG'}, 0, "NAT has already been configured on $computer_name, '$nat_host_chain_name' chain exists in nat table");
		return 1;
	}
	else {
		# Before VCL 2.5, dedicated NAT host chain wasn't created, check if MASQUERADE rule exists
		for my $rule (@{$nat_table_info->{POSTROUTING}{rules}}) {
			my $rule_specification_string = $rule->{rule_specification};
			if ($rule_specification_string =~ /MASQUERADE/) {
				notify($ERRORS{'DEBUG'}, 0, "POSTROUTING chain in nat table contains a MASQUERADE rule, assuming NAT has already been configured: $rule_specification_string");
				return 1;
			}
		}
	}
	
	# Figure out the public and internal interface names
	my $public_interface_name;
	my $internal_interface_name;
	my $public_subnet_mask;
	my $internal_subnet_mask;
	
	my $network_configuration = $self->os->get_network_configuration();
	for my $interface_name (keys %$network_configuration) {
		my @ip_addresses = keys %{$network_configuration->{$interface_name}{ip_address}};
		
		# Check if the interface is assigned the nathost.publicIPaddress
		if (grep { $_ eq $public_ip_address } @ip_addresses) {
			$public_interface_name = $interface_name;
			$public_subnet_mask = $network_configuration->{$interface_name}{ip_address}{$public_ip_address};
		}
		
		# If nathost.internalIPaddress is set, check if interface is assigned matching IP address
		if (grep { $_ eq $internal_ip_address } @ip_addresses) {
			$internal_interface_name = $interface_name;
			$internal_subnet_mask = $network_configuration->{$interface_name}{ip_address}{$internal_ip_address};
		}
	}
	if (!$public_interface_name) {
		notify($ERRORS{'WARNING'}, 0, "failed to configure NAT host $computer_name, no interface is assigned the public IP address configured in the nathost table: $public_ip_address\n" . format_data($network_configuration));
		return;
	}
	if (!$internal_interface_name) {
		notify($ERRORS{'WARNING'}, 0, "failed to configure NAT host $computer_name, no interface is assigned the internal IP address configured in the nathost table: $internal_ip_address\n" . format_data($network_configuration));
		return;
	}
	my ($public_network_address, $public_network_bits) = ip_address_to_network_address($public_ip_address, $public_subnet_mask);
	my ($internal_network_address, $internal_network_bits) = ip_address_to_network_address($internal_ip_address, $internal_subnet_mask);
	notify($ERRORS{'DEBUG'}, 0, "determined NAT host interfaces:\n" .
		"public - interface: $public_interface_name, IP address: $public_ip_address/$public_subnet_mask, network: $public_network_address/$public_network_bits\n" .
		"internal - interface: $internal_interface_name, IP address: $internal_ip_address/$internal_subnet_mask, network: $internal_network_address/$internal_network_bits"
	);
	
	my @natport_ranges = get_natport_ranges();
	my $destination_ports = '';
	for my $natport_range (@natport_ranges) {
		my ($start_port, $end_port) = @$natport_range;
		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";
	}
	
	
	$self->create_chain('filter', $nat_host_chain_name);
	$self->create_chain('nat', $nat_host_chain_name);
	if (!$self->insert_rule('filter', 'INPUT',
		{
			'parameters' => {
				'jump' => $nat_host_chain_name,
			},
			'match_extensions' => {
				'comment' => {
					'comment' => "VCL: jump from filter table INPUT chain to NAT host $nat_host_chain_name chain",
				},
			},
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('filter', 'FORWARD',
		{
			'parameters' => {
				'jump' => $nat_host_chain_name,
			},
			'match_extensions' => {
				'comment' => {
					'comment' => "VCL: jump from filter table FORWARD chain to NAT host $nat_host_chain_name chain",
				},
			},
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('nat', 'POSTROUTING',
		{
			'parameters' => {
				'jump' => $nat_host_chain_name,
			},
			'match_extensions' => {
				'comment' => {
					'comment' => "VCL: jump from nat table POSTROUTING chain to NAT host $nat_host_chain_name chain",
				},
			},
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('nat', $nat_host_chain_name,
		{
			'parameters' => {
				'out-interface' => $public_interface_name,
				'!destination' => "$internal_network_address/$internal_network_bits",
				'jump' => 'MASQUERADE',
			},
			'match_extensions' => {
				'comment' => {
					'comment' => "VCL: change IP of outbound $public_interface_name packets to NAT host IP address $public_ip_address",
				},
			},
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('filter', $nat_host_chain_name,
		{
			'parameters' => {
				'in-interface' => $public_interface_name,
				'destination' => $public_ip_address,
				'jump' => 'ACCEPT',
				'protocol' => 'tcp',
			},
			'match_extensions' => {
				'state' => {
					'state' => 'NEW,RELATED,ESTABLISHED',
				},
				'multiport' => {
					'destination-ports' => $destination_ports,
				},
				'comment' => {
					'comment' => "VCL: allow inbound TCP traffic on the NAT port ranges to public $public_interface_name",
				},
			},
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('filter', $nat_host_chain_name,
		{
			'parameters' => {
				'in-interface' => $public_interface_name,
				'destination' => $public_ip_address,
				'jump' => 'ACCEPT',
				'protocol' => 'udp',
			},
			'match_extensions' => {
				'state' => {
					'state' => 'NEW,RELATED,ESTABLISHED',
				},
				'multiport' => {
					'destination-ports' => $destination_ports,
				},
				'comment' => {
					'comment' => "VCL: allow inbound UDP traffic on the NAT port ranges to public $public_interface_name",
				},
			},
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('filter', $nat_host_chain_name,
		{
			'parameters' => {
				'in-interface' => $public_interface_name,
				'out-interface' => $internal_interface_name,
				'jump' => 'ACCEPT',
			},
			'match_extensions' => {
				'state' => {
					'state' => 'NEW,RELATED,ESTABLISHED',
				},
				'comment' => {
					'comment' => "VCL: forward inbound packets from public $public_interface_name to internal $internal_interface_name",
				},
			},	
		}
	)) {
		return;
	}
	
	if (!$self->insert_rule('filter', $nat_host_chain_name,
		{
			'parameters' => {
				'in-interface' => $internal_interface_name,
				'out-interface' => $public_interface_name,
				'jump' => 'ACCEPT',
			},
			'match_extensions' => {
				'state' => {
					'state' => 'NEW,RELATED,ESTABLISHED',
				},
				'comment' => {
					'comment' => "VCL: forward outbound packets from internal $internal_interface_name to public $public_interface_name",
				},
			},
		}
	)) {
		return;
	}

	
	$self->save_configuration();
	notify($ERRORS{'DEBUG'}, 0, "successfully configured NAT on $computer_name");
	return 1;
}