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/12 21:17:47 UTC

svn commit: r1645057 - in /vcl/trunk/managementnode/lib/VCL: Module/OS.pm Module/OS/Linux.pm Module/OS/Linux/firewall/iptables.pm Module/OS/Windows.pm utils.pm

Author: arkurth
Date: Fri Dec 12 20:17:47 2014
New Revision: 1645057

URL: http://svn.apache.org/r1645057
Log:
VCL-174
Changed iptables.pm::initialize to only check for the iptables command, not the service. The command can be used if firewalld is used.

Updated configure_nat to figure out the internal network address and network bits. The MASQUERADE rule was tightened so that it matches only if the destination address is not on the internal network to allow things to work if the public and internal networks are using the same interface.

Added check to Windows.pm::grant_access when process_connect_methods is called. It was not returning if process_connect_methods failed. As a result, the reservation did not fail when it should have.

Other
Updated Linux.pm::update_public_hostname. It was generating unnecessary warnings if the public IP address did not resolve. Added utils.pm::ip_address_to_hostname. This is called by update_public_hostname instead of running ipcalc. If the hostname can't be resolved the hostname is set to the name in the VCL database.

Updated Linux.pm::set_static_public_address to continue to try to set the default route even if the IP address is correct to begin with.

Modified:
    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/firewall/iptables.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
    vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS.pm?rev=1645057&r1=1645056&r2=1645057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS.pm Fri Dec 12 20:17:47 2014
@@ -2112,7 +2112,7 @@ sub remove_lines_from_file {
 =cut
 
 sub execute {
-#return execute_new(@_);
+	my @original_arguments = @_;
 	my ($argument) = @_;
 	my ($computer_name, $command, $display_output, $timeout_seconds, $max_attempts, $port, $user, $password, $identity_key, $ignore_error);
 	
@@ -2178,6 +2178,11 @@ sub execute {
 		return;
 	}
 	
+	# TESTING: use the new subroutine if $ENV{execute_new} is set and the command isn't one that's known to fail with the new subroutine
+	if ($ENV{execute_new} && $command !~ /(vmkfstools|qemu-img|Convert-VHD|scp)/) {
+		return execute_new(@original_arguments);
+	}
+	
 	my $arguments = {
 		node => $computer_name,
 		command => $command,

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=1645057&r1=1645056&r2=1645057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Fri Dec 12 20:17:47 2014
@@ -644,8 +644,7 @@ sub update_public_hostname {
 		return;
 	}
 	
-	my $management_node_keys = $self->data->get_management_node_keys();
-	my $computer_node_name   = $self->data->get_computer_node_name();
+	my $computer_node_name = $self->data->get_computer_node_name();
 	
 	# Get the IP address of the public adapter
 	my $public_ip_address = $self->get_public_ip_address();
@@ -654,42 +653,11 @@ sub update_public_hostname {
 		return;
 	}
 	notify($ERRORS{'DEBUG'}, 0, "retrieved public IP address of $computer_node_name: $public_ip_address");
-	sleep 5;
 	
 	# Get the hostname for the public IP address
-	my $ipcalc_command = "/bin/ipcalc --hostname $public_ip_address";
-	my ($ipcalc_exit_status, $ipcalc_output) = run_command($ipcalc_command);
-	if (!defined($ipcalc_output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to run ipcalc command on management node to determine public hostname of $computer_node_name, command: '$ipcalc_command'");
-		return;
-	}
-	
-	my ($public_hostname) = ("@$ipcalc_output" =~ /HOSTNAME=(.*)/i);
-	if ($public_hostname) {
-		notify($ERRORS{'DEBUG'}, 0, "determined registered public hostname of $computer_node_name ($public_ip_address) by running ipcalc on the management node: '$public_hostname'");
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "failed to determine registered public hostname of $computer_node_name ($public_ip_address), command: '$ipcalc_command', output:\n" . join("\n", @$ipcalc_output));
-		
-		# Attempt to run the ipcalc command on the host
-		my ($ipcalc_exit_status, $ipcalc_output) = $self->execute($ipcalc_command);
-		if (!defined($ipcalc_output)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to run ipcalc command on $computer_node_name to determine its public hostname, command: '$ipcalc_command'");
-			return;
-		}
-		
-		($public_hostname) = ("@$ipcalc_output" =~ /HOSTNAME=(.*)/i);
-		if ($public_hostname) {
-			notify($ERRORS{'DEBUG'}, 0, "determined registered public hostname of $computer_node_name ($public_ip_address) by running ipcalc on $computer_node_name: '$public_hostname'");
-		}
-		else {
-			notify($ERRORS{'WARNING'}, 0, "failed to determine registered public hostname of $computer_node_name ($public_ip_address) by running ipcalc on either the management node or $computer_node_name, command: '$ipcalc_command', output:\n" . join("\n", @$ipcalc_output));
-			return;
-		}
-	}
+	my $public_hostname = ip_address_to_hostname($public_ip_address) || $computer_node_name;
 	
 	# Set the node's hostname to public hostname
-
 	if ($self->can("update_hostname_file")) {
 		if (!$self->update_hostname_file($public_hostname)) {
 			notify($ERRORS{'WARNING'}, 0, "failed to update hostname file");
@@ -712,10 +680,8 @@ sub update_public_hostname {
 	}
 	
 	return 1;
-
 }
 
-
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 update_hostname_file
@@ -856,43 +822,40 @@ EOF
 	# Get the current public IP address being used by the computer
 	# Pass the $ignore_error flag to prevent warnings if not defined
 	my $current_public_ip_address = $self->get_public_ip_address(1);
-	if ($current_public_ip_address) {
-		if ($current_public_ip_address eq $computer_public_ip_address) {
-			notify($ERRORS{'DEBUG'}, 0, "static public IP address does not need to be set, $computer_name is already configured to use $current_public_ip_address");
-			return 1;
+	if ($current_public_ip_address && $current_public_ip_address eq $computer_public_ip_address) {
+		notify($ERRORS{'DEBUG'}, 0, "static public IP address does not need to be set, $computer_name is already configured to use $current_public_ip_address");
+	}
+	else {
+		if ($current_public_ip_address) {
+			notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, public IP address currently being used by $computer_name $current_public_ip_address does NOT match correct public IP address: $computer_public_ip_address");
 		}
 		else {
-			notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, public IP address currently being used by $computer_name $current_public_ip_address does NOT match correct public IP address: $computer_public_ip_address");
+			notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, unable to determine public IP address currently in use on $computer_name");
 		}
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, unable to determine public IP address currently in use on $computer_name");
-	}
-	
-	
-	# Make sure required info was retrieved
-	if ("$computer_public_ip_address $subnet_mask $default_gateway" =~ /undefined/) {
-		notify($ERRORS{'WARNING'}, 0, "failed to retrieve required network configuration for $computer_name:\n$configuration_info_string");
-		return;
-	}
-	else {
-		notify($ERRORS{'OK'}, 0, "attempting to set static public IP address on $computer_name:\n$configuration_info_string");
-	}
-	
-	# Try to ping address to make sure it's available
-	# FIXME  -- need to add other tests for checking ip_address is or is not available.
-	if ((_pingnode($computer_public_ip_address))) {
-		notify($ERRORS{'WARNING'}, 0, "ip_address $computer_public_ip_address is pingable, can not assign to $computer_name ");
-		return;
-	}
-	
-	# Assemble the ifcfg file path
-	my $network_scripts_path = "/etc/sysconfig/network-scripts";
-	my $ifcfg_file_path      = "$network_scripts_path/ifcfg-$interface_name";
-	notify($ERRORS{'DEBUG'}, 0, "public interface ifcfg file path: $ifcfg_file_path");
-	
-	# Assemble the ifcfg file contents
-	my $ifcfg_contents = <<EOF;
+		
+		# Make sure required info was retrieved
+		if ("$computer_public_ip_address $subnet_mask $default_gateway" =~ /undefined/) {
+			notify($ERRORS{'WARNING'}, 0, "failed to retrieve required network configuration for $computer_name:\n$configuration_info_string");
+			return;
+		}
+		else {
+			notify($ERRORS{'OK'}, 0, "attempting to set static public IP address on $computer_name:\n$configuration_info_string");
+		}
+		
+		# Try to ping address to make sure it's available
+		# FIXME  -- need to add other tests for checking ip_address is or is not available.
+		if ((_pingnode($computer_public_ip_address))) {
+			notify($ERRORS{'WARNING'}, 0, "ip_address $computer_public_ip_address is pingable, can not assign to $computer_name ");
+			return;
+		}
+		
+		# Assemble the ifcfg file path
+		my $network_scripts_path = "/etc/sysconfig/network-scripts";
+		my $ifcfg_file_path      = "$network_scripts_path/ifcfg-$interface_name";
+		notify($ERRORS{'DEBUG'}, 0, "public interface ifcfg file path: $ifcfg_file_path");
+		
+		# Assemble the ifcfg file contents
+		my $ifcfg_contents = <<EOF;
 DEVICE=$interface_name
 BOOTPROTO=static
 IPADDR=$computer_public_ip_address
@@ -902,25 +865,35 @@ STARTMODE=onboot
 ONBOOT=yes
 EOF
 	
-	# Echo the contents to the ifcfg file
-	my $echo_ifcfg_command = "echo \"$ifcfg_contents\" > $ifcfg_file_path";
-	my ($echo_ifcfg_exit_status, $echo_ifcfg_output) = $self->execute($echo_ifcfg_command);
-	if (!defined($echo_ifcfg_output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to run command to recreate $ifcfg_file_path on $computer_name: '$echo_ifcfg_command'");
-		return;
-	}
-	elsif ($echo_ifcfg_exit_status || grep(/echo:/i, @$echo_ifcfg_output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to recreate $ifcfg_file_path on $computer_name, exit status: $echo_ifcfg_exit_status, command: '$echo_ifcfg_command', output:\n" . join("\n", @$echo_ifcfg_output));
-		return;
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "recreated $ifcfg_file_path on $computer_name:\n$ifcfg_contents");
-	}
-	
-	# Restart the interface
-	if (!$self->restart_network_interface($interface_name)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to restart public interface $interface_name on $computer_name");
-		return;
+		# Echo the contents to the ifcfg file
+		my $echo_ifcfg_command = "echo \"$ifcfg_contents\" > $ifcfg_file_path";
+		my ($echo_ifcfg_exit_status, $echo_ifcfg_output) = $self->execute($echo_ifcfg_command);
+		if (!defined($echo_ifcfg_output)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to run command to recreate $ifcfg_file_path on $computer_name: '$echo_ifcfg_command'");
+			return;
+		}
+		elsif ($echo_ifcfg_exit_status || grep(/echo:/i, @$echo_ifcfg_output)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to recreate $ifcfg_file_path on $computer_name, exit status: $echo_ifcfg_exit_status, command: '$echo_ifcfg_command', output:\n" . join("\n", @$echo_ifcfg_output));
+			return;
+		}
+		else {
+			notify($ERRORS{'DEBUG'}, 0, "recreated $ifcfg_file_path on $computer_name:\n$ifcfg_contents");
+		}
+		
+		# Restart the interface
+		if (!$self->restart_network_interface($interface_name)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to restart public interface $interface_name on $computer_name");
+			return;
+		}
+		
+		my $ext_sshd_config_file_path = '/etc/ssh/external_sshd_config';
+		if ($self->file_exists($ext_sshd_config_file_path)) {
+			# Remove existing ListenAddress lines from external_sshd_config
+			$self->remove_lines_from_file($ext_sshd_config_file_path, 'ListenAddress') || return;
+			
+			# Add ListenAddress line to the end of the file
+			$self->append_text_file($ext_sshd_config_file_path, "ListenAddress $computer_public_ip_address\n") || return;
+		}
 	}
 	
 	# Delete existing default route
@@ -956,14 +929,6 @@ EOF
 		notify($ERRORS{'DEBUG'}, 0, "added default route to $default_gateway on public interface $interface_name on $computer_name, output:\n" . format_data($route_add_output));
 	}
 	
-	my $ext_sshd_config_file_path = '/etc/ssh/external_sshd_config';
-	
-	# Remove existing ListenAddress lines from external_sshd_config
-	$self->remove_lines_from_file($ext_sshd_config_file_path, 'ListenAddress') || return;
-	
-	# Add ListenAddress line to the end of the file
-	$self->append_text_file($ext_sshd_config_file_path, "ListenAddress $computer_public_ip_address\n") || return;
-	
 	# Update resolv.conf if DNS server address is configured for the management node
 	my $resolv_conf_path = "/etc/resolv.conf";
 	if (@dns_servers) {
@@ -2401,8 +2366,8 @@ sub get_network_configuration {
 		elsif (!defined($network_configuration->{$interface_name})) {
 			notify($ERRORS{'WARNING'}, 0, "found default gateway for '$interface_name' interface but the network configuration for '$interface_name' was not previously retrieved, route output:\n" . join("\n", @$route_output) . "\nnetwork configuation:\n" . format_data($network_configuration));
 		}
-		elsif (defined($network_configuration->{$interface_name}{default_gateway})) {
-			notify($ERRORS{'WARNING'}, 0, "multiple default gateway are configured for '$interface_name' interface, route output:\n" . join("\n", @$route_output));
+		elsif (defined($network_configuration->{$interface_name}{default_gateway}) && $default_gateway ne $network_configuration->{$interface_name}{default_gateway}) {
+			notify($ERRORS{'WARNING'}, 0, "multiple default gateways are configured for '$interface_name' interface, route output:\n" . join("\n", @$route_output));
 		}
 		else {
 			$network_configuration->{$interface_name}{default_gateway} = $default_gateway;

Modified: 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=1645057&r1=1645056&r2=1645057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm Fri Dec 12 20:17:47 2014
@@ -89,8 +89,8 @@ sub initialize {
 	
 	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");
+	if (!$self->command_exists('iptables')) {
+		notify($ERRORS{'DEBUG'}, 0, ref($self) . " object not initialized to control $computer_name, iptables command does not exist");
 		return 0;
 	}
 	
@@ -641,6 +641,9 @@ sub configure_nat {
 	# 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->get_network_configuration();
 	for my $interface_name (keys %$network_configuration) {
 		my @ip_addresses = keys %{$network_configuration->{$interface_name}{ip_address}};
@@ -648,11 +651,13 @@ sub configure_nat {
 		# 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) {
@@ -663,9 +668,11 @@ sub configure_nat {
 		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: $public_interface_name ($public_ip_address)\n" .
-		"internal: $internal_interface_name: ($internal_ip_address)"
+		"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_variable = get_variable('natport_ranges') || '49152-65535';
@@ -685,6 +692,7 @@ sub configure_nat {
 		'chain' => 'POSTROUTING',
 		'parameters' => {
 			'out-interface' => $public_interface_name,
+			'!destination' => "$internal_network_address/$internal_network_bits",
 			'jump' => 'MASQUERADE',
 		},
 		'match_extensions' => {
@@ -706,7 +714,7 @@ sub configure_nat {
 		},
 		'match_extensions' => {
 			'state' => {
-				'state' => 'RELATED,ESTABLISHED',
+				'state' => 'NEW,RELATED,ESTABLISHED',
 			},
 			'multiport' => {
 				'destination-ports' => $destination_ports,
@@ -726,7 +734,7 @@ sub configure_nat {
 		},
 		'match_extensions' => {
 			'state' => {
-				'state' => 'RELATED,ESTABLISHED',
+				'state' => 'NEW,RELATED,ESTABLISHED',
 			},
 			'multiport' => {
 				'destination-ports' => $destination_ports,

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=1645057&r1=1645056&r2=1645057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm Fri Dec 12 20:17:47 2014
@@ -1101,6 +1101,10 @@ sub grant_access {
 	if ($self->process_connect_methods("", 1) ) {
 		notify($ERRORS{'OK'}, 0, "processed connection methods on $computer_node_name");
 	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to process connection methods on $computer_node_name");
+		return;
+	}
 
 	# If this is an imaging request, make sure the Administrator account is enabled
 	if ($request_forimaging) {

Modified: vcl/trunk/managementnode/lib/VCL/utils.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1645057&r1=1645056&r2=1645057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/utils.pm Fri Dec 12 20:17:47 2014
@@ -78,7 +78,8 @@ use XML::Simple;
 use Time::HiRes qw(gettimeofday tv_interval);
 use Crypt::OpenSSL::RSA;
 use B qw(svref_2object);
-use Socket qw(inet_ntoa);
+use Socket;
+use Net::Netmask;
 
 BEGIN {
 	$ENV{PERL_RL} = 'Perl';
@@ -186,6 +187,8 @@ our @EXPORT = qw(
 	insert_reload_request
 	insert_request
 	insertloadlog
+	ip_address_to_hostname
+	ip_address_to_network_address
 	is_inblockrequest
 	is_ip_assigned_query
 	is_management_node_process_running
@@ -3990,8 +3993,11 @@ sub run_ssh_command {
 	$port = 22 if (!$port);
 	$timeout_seconds = 0 if (!$timeout_seconds);
 	$identity_paths = get_management_node_info()->{keys} if (!defined $identity_paths || length($identity_paths) == 0);
-
-#return VCL::Module::OS::execute_new($node, $command, $output_level, $timeout_seconds, $max_attempts, $port, $user, '', $identity_paths);
+	
+	# TESTING: use the new subroutine if $ENV{execute_new} is set and the command isn't one that's known to fail with the new subroutine
+	if ($ENV{execute_new} && $command !~ /(vmkfstools|qemu-img|Convert-VHD|scp)/) {
+		return VCL::Module::OS::execute_new($node, $command, $output_level, $timeout_seconds, $max_attempts, $port, $user, '', $identity_paths);
+	}
 	
 	# TODO: Add ssh path to config file and set global variable
 	# Locate the path to the ssh binary
@@ -12401,6 +12407,33 @@ sub hostname_to_ip_address {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 ip_address_to_hostname
+
+ Parameters  : $ip_address
+ Returns     : string
+ Description : Calls gethostbyaddr on the management node to determine the
+               hostname an IP address resolves to. The hostname is returned. If
+               the IP address cannot be resolved or if an error occurs, null is
+               returned.
+
+=cut
+
+sub ip_address_to_hostname {
+	my ($ip_address) = @_;
+	
+	my $hostname = gethostbyaddr(inet_aton($ip_address), AF_INET);
+	if (defined $hostname) {
+		notify($ERRORS{'DEBUG'}, 0, "determined hostname from IP address $ip_address: $hostname");
+		return $hostname;
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "unable to determine hostname from IP address: $ip_address");
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 update_request_checkuser
 
  Parameters  : $request_id, $checkuser
@@ -12540,6 +12573,45 @@ EOF
 }
 
 #/////////////////////////////////////////////////////////////////////////////
+
+=head2 ip_address_to_network_address
+
+ Parameters  : $ip_address, $subnet_mask
+ Returns     : If called in array context: ($network_address, $network_bits)
+               If called in scalar context: $network_address
+ Description : Determines a network address and network bits.
+
+=cut
+
+sub ip_address_to_network_address {
+	my ($ip_address, $subnet_mask) = @_;
+	if (!defined($ip_address)) {
+		notify($ERRORS{'WARNING'}, 0, "$ip_address argument was not supplied");
+		return;
+	}
+	if (!defined($subnet_mask)) {
+		notify($ERRORS{'WARNING'}, 0, "$subnet_mask argument was not supplied");
+		return;
+	}
+	
+	my $netmask_object = new Net::Netmask("$ip_address/$subnet_mask");
+	if(!$netmask_object) {
+		notify($ERRORS{'WARNING'}, 0, "failed to create Net::Netmask object, IP address: $ip_address, subnet mask: $subnet_mask");
+		return;
+	}
+	
+	my $network_address = $netmask_object->base();
+	my $network_bits = $netmask_object->bits();
+	notify($ERRORS{'DEBUG'}, 0, "$ip_address/$subnet_mask --> $network_address/$network_bits");
+	if (wantarray) {
+		return ($network_address, $network_bits);
+	}
+	else {
+		return $network_address;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
 
 1;
 __END__