diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 5b235a5f..a2366e3f 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1043,6 +1043,7 @@ sub _set_paths pxe_grub => "/var/lib/tftpboot/grub.cfg", postfix_main => "/etc/postfix/main.cf", postfix_relay_password => "/etc/postfix/relay_password", + 'qemu.conf' => "/etc/libvirt/qemu.conf", ssh_config => "/etc/ssh/ssh_config", 'type.striker' => "/etc/anvil/type.striker", 'type.dr' => "/etc/anvil/type.dr", @@ -1169,7 +1170,7 @@ sub _set_paths ip => "/usr/sbin/ip", 'ipmi-oem' => "/usr/sbin/ipmi-oem", ipmitool => "/usr/bin/ipmitool", - ### NOTE: When System->manage_firewall() is done, search for and replace all + ### NOTE: When Network->manage_firewall() is done, search for and replace all ### instances where iptables is called and replace with firewall-cmd ### calls iptables => "/usr/sbin/iptables", diff --git a/Anvil/Tools/DRBD.pm b/Anvil/Tools/DRBD.pm index 7c2e892c..518502d8 100644 --- a/Anvil/Tools/DRBD.pm +++ b/Anvil/Tools/DRBD.pm @@ -2023,7 +2023,7 @@ sub manage_resource } } - # If we 'adjust'ed abovem this will likely complain that the backing disk already exists, and that's + # If we 'adjust'ed above, this will likely complain that the backing disk already exists, and that's # fine. my $shell_call = $anvil->data->{path}{exe}{drbdadm}." ".$task." ".$resource; my $output = ""; diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index 9076a0e7..6b5e2404 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -1154,12 +1154,7 @@ sub configure_pgsql } # Make sure the psql TCP port is open. - $anvil->data->{database}{$uuid}{port} = 5432 if not $anvil->data->{database}{$uuid}{port}; - my $port_status = $anvil->System->manage_firewall({ - task => "open", - port_number => $anvil->data->{database}{$uuid}{port}, - }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { port_status => $port_status }}); + $anvil->Network->manage_firewall({debug => $debug}); return(0); } @@ -14322,8 +14317,9 @@ sub load_database my $start_time = time; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { start_time => $start_time }}); - ### TODO: Replace this with System->manage_firewall(). # Throw up the firewall. Have the open call ready in case we hit an error. + $anvil->Network->manage_firewall({debug => $debug}); + ### TODO: Delete this when done with manage_firewall(). my $block_call = $anvil->data->{path}{exe}{iptables}." -I INPUT -p tcp --dport 5432 -j REJECT"; my $open_call = $anvil->data->{path}{exe}{iptables}." -D INPUT -p tcp --dport 5432 -j REJECT"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { block_call => $block_call }}); diff --git a/Anvil/Tools/Get.pm b/Anvil/Tools/Get.pm index 187eef77..c2d99aaf 100644 --- a/Anvil/Tools/Get.pm +++ b/Anvil/Tools/Get.pm @@ -769,7 +769,9 @@ sub bridges my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Get->bridges()" }}); - my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{bridge}." -json -details link show"}); + my $shell_call = $anvil->data->{path}{exe}{bridge}." -json -details link show"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code, @@ -899,13 +901,19 @@ sub bridges my $type = "interface"; my $interface = $hash_ref->{ifname}; my $master_bridge = $hash_ref->{master}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + interface => $interface, + master_bridge => $master_bridge, + }}); + + $anvil->data->{$host}{network}{bridges}{bridge}{$master_bridge}{found} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "${host}::network::bridges::bridge::${master_bridge}::found" => $anvil->data->{$host}{network}{bridges}{bridge}{$master_bridge}{found}, + }}); if ($interface eq $master_bridge) { $type = "bridge"; - $anvil->data->{$host}{network}{bridges}{bridge}{$interface}{found} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "${host}::network::bridges::bridge::${interface}::found" => $anvil->data->{$host}{network}{bridges}{bridge}{$interface}{found}, - }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { type => $type }}); } else { diff --git a/Anvil/Tools/Network.pm b/Anvil/Tools/Network.pm index 9b3e437e..2083e09e 100644 --- a/Anvil/Tools/Network.pm +++ b/Anvil/Tools/Network.pm @@ -14,22 +14,34 @@ my $THIS_FILE = "Network.pm"; ### Methods; # bridge_info -# check_internet +# check_firewall # check_network +# check_internet # download # find_matches # find_target_ip # get_company_from_mac -# get_ips # get_ip_from_mac +# get_ips # get_network # is_local # is_our_interface +# is_ip_in_network # load_interfces # load_ips -# is_ip_in_network +# manage_firewall # ping # read_nmcli +# _check_firewalld_conf +# _get_existing_zone_interfaces +# _get_server_ports +# _get_drbd_ports +# _get_live_migration_ports +# _manage_port +# _manage_service +# _manage_dr_firewall +# _manage_node_firewall +# _manage_striker_firewall =pod @@ -224,8 +236,69 @@ sub bridge_info } +=head2 check_firewall + +This checks to see if the firewall is running. If it is not, and if C<< sys::daemons::restart_firewalld >> is not set to C<< 0 >>, it will start the firewall. + +It returns C<< 1 >>, the firewall is running. If it returns C<< 0 >>, it is not. + +This method takes no parameters. + +=cut +sub check_firewall +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->check_firewall()" }}); + + my $running = 0; + + # Make sure firewalld is running. + my $firewalld_running = $anvil->System->check_daemon({daemon => $anvil->data->{sys}{daemon}{firewalld}}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { firewalld_running => $firewalld_running }}); + if ($firewalld_running) + { + $running = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { running => $running }}); + } + else + { + if ($anvil->data->{sys}{daemons}{restart_firewalld}) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0127"}); + my $return_code = $anvil->System->start_daemon({daemon => $anvil->data->{sys}{daemon}{firewalld}}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { return_code => $return_code }}); + if ($return_code) + { + # non-0 means something went wrong. + return("!!error!!"); + } + else + { + # Started + $running = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { running => $running }}); + } + } + else + { + # We've been asked to leave it off. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, priority => "alert", key => "log_0128"}); + return(0); + } + } + + + return($running); +} + + =head2 check_network +B<< NOTE >>: This method is not yet implemented. + This method checks to see if bridges and the links in bonds are up. It can simply report the bridge, bond and link states, or it can try to bring up interfaces that are down. This method returns C<< 0 >> if nothing was done. It returns C<< 1 >> if any repairs were done. @@ -544,7 +617,7 @@ sub check_network foreach my $bond (sort {$a cmp $b} keys %{$anvil->data->{bond_health}}) { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "s1:bond_health::${bond}::name" => $anvil->data->{bond_health}{$bond}{name}, "s2:bond_health::${bond}::up" => $anvil->data->{bond_health}{$bond}{up}, "s3:bond_health::${bond}::configured_links" => $anvil->data->{bond_health}{$bond}{configured_links}, @@ -553,7 +626,7 @@ sub check_network }}); foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{bond_health}{$bond}{interface}}) { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "s1:bond_health::${bond}::interface::${interface}::name" => $anvil->data->{bond_health}{$bond}{interface}{$interface}{name}, "s2:bond_health::${bond}::interface::${interface}::in_bond" => $anvil->data->{bond_health}{$bond}{interface}{$interface}{in_bond}, "s3:bond_health::${bond}::interface::${interface}::up" => $anvil->data->{bond_health}{$bond}{interface}{$interface}{up}, @@ -1271,81 +1344,60 @@ sub find_target_ip } -=head2 load_interfces - -This loads all network information for the given host UUID. - -The main difference from C<< ->load_ips() >> is that this method loads information about all interfaces, regardless of if they have an IP, as well as their link state and link information. - -The loaded data will be stored as: +=head2 get_company_from_mac -* C<< machine::::interface:::: +This takes a MAC address (or the first six bytes) and returns the company that owns the OUI. If the company name is not found, an expty string is returned. Parameters; -=head3 clear (optional, default '1') - -When set, any previously known information is cleared. Specifically, the C<< network::> >> hash is deleted prior to the load. To prevent this, set this to C<< 0 >>. - -=head3 host (optional, default is 'host_uuid' value) - -This is the optional C<< target >> string to use in the hash where the data is stored. - -=head3 host_uuid (optional, default 'sys::host_uuid') +=head3 mac (required) -This is the C<< host_uuid >> of the hosts whose IP and interface data that you want to load. The default is to load the local machine's data. +This is the first six bytes of the mac address, C<< xx:xx:xx >> format, being searched for. =cut -sub load_interfces +sub get_company_from_mac { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->load_interfces()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_company_from_mac()" }}); - my $clear = defined $parameter->{clear} ? $parameter->{clear} : 1; - my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : $anvil->data->{sys}{host_uuid}; - my $host = defined $parameter->{host} ? $parameter->{host} : ""; + my $mac = defined $parameter->{mac} ? lc($parameter->{mac}) : ""; + my $company = ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - clear => $clear, - host => $host, - host_uuid => $host_uuid, + mac => $mac, }}); - if (not $host_uuid) + if (not $mac) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->load_interfces()", parameter => "host_uuid" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->get_company_from_mac()", parameter => "mac_prefix" }}); return(""); } - if (not $host) + # Have I already looked this one up? + if ($anvil->data->{cache}{mac_to_oui}{$mac}) { - $host = $host_uuid; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); + # Yup, no need to process. + return($anvil->data->{cache}{mac_to_oui}{$mac}); } - if (($clear) && (exists $anvil->data->{network}{$host})) + my $valid_mac = $anvil->Validate->mac({mac => $mac}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac => $mac }}); + if ($valid_mac) { - delete $anvil->data->{network}{$host}; + # Strip the first six bytes. + $mac = ($mac =~ /^([0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2})/i)[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac => $mac }}); + } + elsif ($mac !~ /[0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}/i) + { + # Bad format + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0104", variables => { mac => $mac }}); + return(""); } - # Now load bridge info - my $query = " -SELECT - bridge_uuid, - bridge_name, - bridge_id, - bridge_mac_address, - bridge_mtu, - bridge_stp_enabled -FROM - bridges -WHERE - bridge_id != 'DELETED' -AND - bridge_host_uuid = ".$anvil->Database->quote($host_uuid)." -;"; + my $query = "SELECT oui_company_name FROM oui WHERE oui_mac_prefix = ".$anvil->Database->quote(lc($mac)).";"; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); my $count = @{$results}; @@ -1353,357 +1405,1299 @@ AND results => $results, count => $count, }}); - foreach my $row (@{$results}) + if ($count) { - my $bridge_uuid = defined $row->[0] ? $row->[0] : ""; - my $bridge_name = defined $row->[1] ? $row->[1] : ""; - my $bridge_id = defined $row->[2] ? $row->[2] : ""; - my $bridge_mac_address = defined $row->[3] ? $row->[3] : ""; - my $bridge_mtu = defined $row->[4] ? $row->[4] : ""; - my $bridge_stp_enabled = defined $row->[5] ? $row->[5] : ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - bridge_uuid => $bridge_uuid, - bridge_name => $bridge_name, - bridge_id => $bridge_id, - bridge_mac_address => $bridge_mac_address, - bridge_mtu => $bridge_mtu, - bridge_stp_enabled => $bridge_stp_enabled, - }}); - - # Record the bridge_uuid -> name - $anvil->data->{network}{$host}{bridge_uuid}{$bridge_uuid}{name} = $bridge_name; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::bridge_uuid::${bridge_uuid}::name" => $anvil->data->{network}{$host}{bridge_uuid}{$bridge_uuid}{name}, - }}); - - # We'll initially load empty strings for what would be the IP information. Any interface with IPs will be populated when we call - $anvil->data->{network}{$host}{interface}{$bridge_name}{uuid} = $bridge_uuid; - $anvil->data->{network}{$host}{interface}{$bridge_name}{id} = $bridge_id; - $anvil->data->{network}{$host}{interface}{$bridge_name}{mac_address} = $bridge_mac_address; - $anvil->data->{network}{$host}{interface}{$bridge_name}{mtu} = $bridge_mtu; - $anvil->data->{network}{$host}{interface}{$bridge_name}{stp_enabled} = $bridge_stp_enabled; - $anvil->data->{network}{$host}{interface}{$bridge_name}{type} = "bridge"; - $anvil->data->{network}{$host}{interface}{$bridge_name}{interfaces} = []; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${bridge_name}::uuid" => $anvil->data->{network}{$host}{interface}{$bridge_name}{uuid}, - "network::${host}::interface::${bridge_name}::id" => $anvil->data->{network}{$host}{interface}{$bridge_name}{id}, - "network::${host}::interface::${bridge_name}::mac_address" => $anvil->data->{network}{$host}{interface}{$bridge_name}{mac_address}, - "network::${host}::interface::${bridge_name}::mtu" => $anvil->data->{network}{$host}{interface}{$bridge_name}{mtu}, - "network::${host}::interface::${bridge_name}::stp_enabled" => $anvil->data->{network}{$host}{interface}{$bridge_name}{stp_enabled}, - "network::${host}::interface::${bridge_name}::type" => $anvil->data->{network}{$host}{interface}{$bridge_name}{type}, - }}); + $company = $results->[0]->[0]; + $anvil->data->{cache}{mac_to_oui}{$mac} = $company; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { company => $company }}); } - # Now load bond info - $query = " -SELECT - bond_uuid, - bond_name, - bond_mode, - bond_mtu, - bond_primary_interface, - bond_primary_reselect, - bond_active_interface, - bond_mii_polling_interval, - bond_up_delay, - bond_down_delay, - bond_mac_address, - bond_operational, - bond_bridge_uuid -FROM - bonds WHERE bond_mode != 'DELETED' -AND - bond_host_uuid = ".$anvil->Database->quote($host_uuid)." -;"; + return($company); +} + + +=head2 get_ip_from_mac + +This takes a MAC address and tries to convert it to an IP address. If no IP is found, an empty string is returned. + +Parameters; + +=head3 mac (required) + +This is the MAC address we're looking for an IP to match to. The format must be C<< aa:bb:cc:dd:ee:ff >>. + +=cut +sub get_ip_from_mac +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_ip_from_mac()" }}); + + my $ip = ""; + my $mac = defined $parameter->{mac} ? $parameter->{mac} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + mac => $mac, + }}); + + my $query = "SELECT mac_to_ip_ip_address FROM mac_to_ip WHERE mac_to_ip_mac_address = ".$anvil->Database->quote(lc($mac)).";"; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); - $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); - $count = @{$results}; + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { results => $results, count => $count, }}); - foreach my $row (@{$results}) + if ($count) { - my $bond_uuid = defined $row->[0] ? $row->[0] : ""; - my $bond_name = defined $row->[1] ? $row->[1] : ""; - my $bond_mode = defined $row->[2] ? $row->[2] : ""; - my $bond_mtu = defined $row->[3] ? $row->[3] : ""; - my $bond_primary_interface = defined $row->[4] ? $row->[4] : ""; - my $bond_primary_reselect = defined $row->[5] ? $row->[5] : ""; - my $bond_active_interface = defined $row->[6] ? $row->[6] : ""; - my $bond_mii_polling_interval = defined $row->[7] ? $row->[7] : ""; - my $bond_up_delay = defined $row->[8] ? $row->[8] : ""; - my $bond_down_delay = defined $row->[9] ? $row->[9] : ""; - my $bond_mac_address = defined $row->[10] ? $row->[10] : ""; - my $bond_operational = defined $row->[11] ? $row->[11] : ""; - my $bond_bridge_uuid = defined $row->[12] ? $row->[12] : ""; - my $bridge_name = ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - bond_uuid => $bond_uuid, - bond_name => $bond_name, - bond_mode => $bond_mode, - bond_mtu => $bond_mtu, - bond_primary_interface => $bond_primary_interface, - bond_primary_reselect => $bond_primary_reselect, - bond_active_interface => $bond_active_interface, - bond_mii_polling_interval => $bond_mii_polling_interval, - bond_up_delay => $bond_up_delay, - bond_down_delay => $bond_down_delay, - bond_mac_address => $bond_mac_address, - bond_operational => $bond_operational, - bond_bridge_uuid => $bond_bridge_uuid, - }}); - - # If this bond is connected to a bridge, get the bridge name. - if (($bond_bridge_uuid) && (defined $anvil->data->{network}{$host}{bridge_uuid}{$bond_bridge_uuid}{name})) - { - $bridge_name = $anvil->data->{network}{$host}{bridge_uuid}{$bond_bridge_uuid}{name}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - bond_bridge_uuid => $bond_bridge_uuid, - bridge_name => $bridge_name, - }}); - push @{$anvil->data->{network}{$host}{interface}{$bridge_name}{interfaces}}, $bond_name; - } - - # Record the bond_uuid -> name - $anvil->data->{network}{$host}{bond_uuid}{$bond_uuid}{name} = $bond_name; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::bond_uuid::${bond_uuid}::name" => $anvil->data->{network}{$host}{bond_uuid}{$bond_uuid}{name}, - }}); - - # We'll initially load empty strings for what would be the IP information. Any interface with IPs will be populated when we call - $anvil->data->{network}{$host}{interface}{$bond_name}{uuid} = $bond_uuid; - $anvil->data->{network}{$host}{interface}{$bond_name}{mode} = $bond_mode; - $anvil->data->{network}{$host}{interface}{$bond_name}{mtu} = $bond_mtu; - $anvil->data->{network}{$host}{interface}{$bond_name}{primary_interface} = $bond_primary_interface; - $anvil->data->{network}{$host}{interface}{$bond_name}{primary_reselect} = $bond_primary_reselect; - $anvil->data->{network}{$host}{interface}{$bond_name}{active_interface} = $bond_active_interface; - $anvil->data->{network}{$host}{interface}{$bond_name}{mii_polling_interval} = $bond_mii_polling_interval; - $anvil->data->{network}{$host}{interface}{$bond_name}{up_delay} = $bond_up_delay; - $anvil->data->{network}{$host}{interface}{$bond_name}{down_delay} = $bond_down_delay; - $anvil->data->{network}{$host}{interface}{$bond_name}{mac_address} = $bond_mac_address; - $anvil->data->{network}{$host}{interface}{$bond_name}{operational} = $bond_operational; - $anvil->data->{network}{$host}{interface}{$bond_name}{bridge_uuid} = $bond_bridge_uuid; - $anvil->data->{network}{$host}{interface}{$bond_name}{type} = "bond"; - $anvil->data->{network}{$host}{interface}{$bond_name}{interfaces} = []; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${bond_name}::uuid" => $anvil->data->{network}{$host}{interface}{$bond_name}{uuid}, - "network::${host}::interface::${bond_name}::mode" => $anvil->data->{network}{$host}{interface}{$bond_name}{mode}, - "network::${host}::interface::${bond_name}::mtu" => $anvil->data->{network}{$host}{interface}{$bond_name}{mtu}, - "network::${host}::interface::${bond_name}::primary_interface" => $anvil->data->{network}{$host}{interface}{$bond_name}{primary_interface}, - "network::${host}::interface::${bond_name}::primary_reselect" => $anvil->data->{network}{$host}{interface}{$bond_name}{primary_reselect}, - "network::${host}::interface::${bond_name}::active_interface" => $anvil->data->{network}{$host}{interface}{$bond_name}{active_interface}, - "network::${host}::interface::${bond_name}::mii_polling_interval" => $anvil->data->{network}{$host}{interface}{$bond_name}{mii_polling_interval}, - "network::${host}::interface::${bond_name}::up_delay" => $anvil->data->{network}{$host}{interface}{$bond_name}{up_delay}, - "network::${host}::interface::${bond_name}::down_delay" => $anvil->data->{network}{$host}{interface}{$bond_name}{down_delay}, - "network::${host}::interface::${bond_name}::mac_address" => $anvil->data->{network}{$host}{interface}{$bond_name}{mac_address}, - "network::${host}::interface::${bond_name}::operational" => $anvil->data->{network}{$host}{interface}{$bond_name}{operational}, - "network::${host}::interface::${bond_name}::bridge_uuid" => $anvil->data->{network}{$host}{interface}{$bond_name}{bridge}, - "network::${host}::interface::${bond_name}::type" => $anvil->data->{network}{$host}{interface}{$bond_name}{type}, - }}); - } - - # The order will allow us to show the order in which the interfaces were changed, which the user can - # use to track interfaces as they unplug and plug cables back in. - my $order = 1; - $query = " -SELECT - network_interface_uuid, - network_interface_mac_address, - network_interface_name, - network_interface_speed, - network_interface_mtu, - network_interface_link_state, - network_interface_operational, - network_interface_duplex, - network_interface_medium, - network_interface_bond_uuid, - network_interface_bridge_uuid -FROM - network_interfaces -WHERE - network_interface_operational != 'DELETED' -AND - network_interface_host_uuid = ".$anvil->Database->quote($host_uuid)." -ORDER BY - modified_date DESC -;"; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); - $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); - $count = @{$results}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - results => $results, - count => $count, - }}); - my $changed_order = 1; - foreach my $row (@{$results}) - { - my $network_interface_uuid = defined $row->[0] ? $row->[0] : ""; - my $network_interface_mac_address = defined $row->[1] ? $row->[1] : ""; - my $network_interface_name = defined $row->[2] ? $row->[2] : ""; - my $network_interface_speed = defined $row->[3] ? $row->[3] : ""; - my $network_interface_mtu = defined $row->[4] ? $row->[4] : ""; - my $network_interface_link_state = defined $row->[5] ? $row->[5] : ""; - my $network_interface_operational = defined $row->[6] ? $row->[6] : ""; - my $network_interface_duplex = defined $row->[7] ? $row->[7] : ""; - my $network_interface_medium = defined $row->[8] ? $row->[8] : ""; - my $network_interface_bond_uuid = defined $row->[9] ? $row->[9] : ""; - my $network_interface_bridge_uuid = defined $row->[10] ? $row->[10] : ""; - my $bond_name = ""; - my $bridge_name = ""; - my $this_change_orger = 0; - if (($network_interface_name =~ /^virbr/) or ($network_interface_name =~ /^vnet/)) - { - # This isn't a physical NIC, so it doesn't get a changed order - } - else - { - $this_change_orger = $changed_order++; - } - if (($network_interface_bond_uuid) && (defined $anvil->data->{network}{$host}{bond_uuid}{$network_interface_bond_uuid}{name})) - { - $bond_name = $anvil->data->{network}{$host}{bond_uuid}{$network_interface_bond_uuid}{name}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - network_interface_name => $network_interface_name, - bond_name => $bond_name, - }}); - push @{$anvil->data->{network}{$host}{interface}{$bond_name}{interfaces}}, $network_interface_name; - } - if (($network_interface_bridge_uuid) && (defined $anvil->data->{network}{$host}{bridge_uuid}{$network_interface_bridge_uuid}{name})) - { - $bridge_name = $anvil->data->{network}{$host}{bridge_uuid}{$network_interface_bridge_uuid}{name}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - network_interface_name => $network_interface_name, - bridge_name => $bridge_name, - }}); - push @{$anvil->data->{network}{$host}{interface}{$bridge_name}{interfaces}}, $network_interface_name; - } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - network_interface_uuid => $network_interface_uuid, - network_interface_mac_address => $network_interface_mac_address, - network_interface_name => $network_interface_name, - network_interface_speed => $network_interface_speed, - network_interface_mtu => $network_interface_mtu, - network_interface_link_state => $network_interface_link_state, - network_interface_operational => $network_interface_operational, - network_interface_duplex => $network_interface_duplex, - network_interface_medium => $network_interface_medium, - network_interface_bond_uuid => $network_interface_bond_uuid, - network_interface_bridge_uuid => $network_interface_bridge_uuid, - bond_name => $bond_name, - changed_order => $this_change_orger, - }}); - - # We'll initially load empty strings for what would be the IP information. Any interface with IPs will be populated when we call - $anvil->data->{network}{$host}{interface}{$network_interface_name}{uuid} = $network_interface_uuid; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{mac_address} = $network_interface_mac_address; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{speed} = $network_interface_speed; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{mtu} = $network_interface_mtu; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{link_state} = $network_interface_link_state; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{operational} = $network_interface_operational; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{duplex} = $network_interface_duplex; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{medium} = $network_interface_medium; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_uuid} = $network_interface_bond_uuid; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_name} = $bond_name; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_uuid} = $network_interface_bridge_uuid; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_name} = $bridge_name; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{type} = "interface"; - $anvil->data->{network}{$host}{interface}{$network_interface_name}{changed_order} = $this_change_orger; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${network_interface_name}::uuid" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{uuid}, - "network::${host}::interface::${network_interface_name}::mac_address" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{mac_address}, - "network::${host}::interface::${network_interface_name}::speed" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{speed}, - "network::${host}::interface::${network_interface_name}::mtu" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{mtu}, - "network::${host}::interface::${network_interface_name}::link_state" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{link_state}, - "network::${host}::interface::${network_interface_name}::operational" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{operational}, - "network::${host}::interface::${network_interface_name}::duplex" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{duplex}, - "network::${host}::interface::${network_interface_name}::medium" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{medium}, - "network::${host}::interface::${network_interface_name}::bond_uuid" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_uuid}, - "network::${host}::interface::${network_interface_name}::bond_name" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_name}, - "network::${host}::interface::${network_interface_name}::bridge_uuid" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_uuid}, - "network::${host}::interface::${network_interface_name}::bridge_name" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_name}, - "network::${host}::interface::${network_interface_name}::type" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{type}, - "network::${host}::interface::${network_interface_name}::changed_order" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{changed_order}, - }}); + $ip = $results->[0]->[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip => $ip }}); } - # Load the IPs - $anvil->Network->load_ips({ - debug => $debug, - host_uuid => $host_uuid, - host => $host, - clear => 0, - }); - - return(0); + return($ip); } -=head2 load_ips -This method loads and stores the same data as the C<< get_ips >> method, but does so by loading data from the database, instead of collecting it directly from the host. As such, it can also be used by C<< find_matches >>. +=head2 get_ips -C<< Note >>: IP addresses that have been deleted will be marked so by C<< ip >> being set to C<< DELETED >>. +This method checks the local system for interfaces and stores them in: -The loaded data will be stored as: +* C<< network::::interface::::ip >> - If an IP address is set +* C<< network::::interface::::subnet_mask >> - If an IP is set +* C<< network::::interface::::mac_address >> - Always set. +* C<< network::::interface::::mtu >> - Always set. +* C<< network::::interface::::default_gateway >> = C<< 0 >> if not the default gateway, C<< 1 >> if so. +* C<< network::::interface::::gateway >> = If the default gateway, this is the gateway IP address. +* C<< network::::interface::::dns >> = If the default gateway, this is the comma-separated list of active DNS servers. -* C<< network::::interface::::ip >> - If an IP address is set -* C<< network::::interface::::subnet_mask >> - If an IP is set -* C<< network::::interface::::mac >> - Always set. -* C<< network::::interface::::default_gateway >> = C<< 0 >> if not the default gateway, C<< 1 >> if so. -* C<< network::::interface::::gateway >> = If the default gateway, this is the gateway IP address. -* C<< network::::interface::::dns >> = If the default gateway, this is the comma-separated list of active DNS servers. +To make it convenient to translate a MAC address into interface names, this hash is also stored; + +* C<< network::::mac_address::::interface = Interface name. + +When called without a C<< target >>, C<< local >> is used. + +To aid in look-up by MAC address, C<< network::mac_address::::iface >> is also set. Note that this is not target-dependent. Parameters; -=head3 clear (optional, default '1') +=head3 password (optional) -When set, any previously known information is cleared. Specifically, the C<< network::> >> hash is deleted prior to the load. To prevent this, set this to C<< 0 >>. +If C<< target >> is set, this is the password used to log into the remote system as the C<< remote_user >>. If it is not set, an attempt to connect without a password will be made (though this will usually fail). -=head3 host (optional, default is 'host_uuid' value) +=head3 port (optional, default 22) -This is the optional C<< host >> string to use in the hash where the data is stored. +If C<< target >> is set, this is the TCP port number used to connect to the remote machine. -=head3 host_uuid (optional, default 'sys::host_uuid') +=head3 remote_user (optional) -This is the C<< host_uuid >> of the hosts whose IP and interface data that you want to load. The default is to load the local machine's data. +If C<< target >> is set, this is the user account that will be used when connecting to the remote system. + +=head3 target (optional) + +If set, the file will be read from the target machine. This must be either an IP address or a resolvable host name. + +The file will be copied to the local system using C<< $anvil->Storage->rsync() >> and stored in C<< /tmp/. >>. if C<< cache >> is set, the file will be preserved locally. Otherwise it will be deleted once it has been read into memory. + +B<< Note >>: the temporary file will be prefixed with the path to the file name, with the C<< / >> converted to C<< _ >>. =cut -sub load_ips +sub get_ips { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->load_ips()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_ips()" }}); - my $clear = defined $parameter->{clear} ? $parameter->{clear} : 1; - my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : $anvil->Get->host_uuid; - my $host = defined $parameter->{host} ? $parameter->{host} : ""; + my $password = defined $parameter->{password} ? $parameter->{password} : ""; + my $port = defined $parameter->{port} ? $parameter->{port} : 22; + my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; + my $target = defined $parameter->{target} ? $parameter->{target} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - clear => $clear, - host => $host, - host_uuid => $host_uuid, + password => $anvil->Log->is_secure($password), + port => $port, + remote_user => $remote_user, + target => $target, }}); - if (not $host_uuid) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->load_ips()", parameter => "ip" }}); - return(""); - } - - if (not $host) - { - $host = $host_uuid; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); - } + # This is used in the hash reference when storing the data. + my $host = $target ? $target : $anvil->Get->short_host_name(); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); - if (($clear) && (exists $anvil->data->{network}{$host})) + if (exists $anvil->data->{network}{$host}) { delete $anvil->data->{network}{$host}; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0700", variables => { hash => "network::${host}" }}); } - # Read in all IPs, so that we know which to remove. + # Reading locally or remote? + my $in_iface = ""; + my $shell_call = $anvil->data->{path}{exe}{ip}." addr list"; + my $output = ""; + my $is_local = $anvil->Network->is_local({host => $target}); + if ($is_local) + { + # Local call. + ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, + }}); + } + else + { + # Remote call + ($output, my $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + user => $remote_user, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:error' => $error, + 's3:return_code' => $return_code, + }}); + } + foreach my $line (split/\n/, $output) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /^\d+: (.*?): /) + { + $in_iface = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { in_iface => $in_iface }}); + + $anvil->data->{network}{$host}{interface}{$in_iface}{ip} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{ip}; + $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask}; + $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}; + $anvil->data->{network}{$host}{interface}{$in_iface}{mtu} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{mtu}; + $anvil->data->{network}{$host}{interface}{$in_iface}{default_gateway} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{default_gateway}; + $anvil->data->{network}{$host}{interface}{$in_iface}{gateway} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{gateway}; + $anvil->data->{network}{$host}{interface}{$in_iface}{dns} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{dns}; + $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes}; + $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${in_iface}::ip" => $anvil->data->{network}{$host}{interface}{$in_iface}{ip}, + "network::${host}::interface::${in_iface}::subnet_mask" => $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask}, + "network::${host}::interface::${in_iface}::mac_address" => $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}, + "network::${host}::interface::${in_iface}::mtu" => $anvil->data->{network}{$host}{interface}{$in_iface}{mtu}, + "network::${host}::interface::${in_iface}::default_gateway" => $anvil->data->{network}{$host}{interface}{$in_iface}{default_gateway}, + "network::${host}::interface::${in_iface}::gateway" => $anvil->data->{network}{$host}{interface}{$in_iface}{gateway}, + "network::${host}::interface::${in_iface}::dns" => $anvil->data->{network}{$host}{interface}{$in_iface}{dns}, + }}); + + if ($in_iface ne "lo") + { + # Read the read and write bytes. + my $read_bytes = 0; + my $write_bytes = 0; + my $shell_call = " +if [ -e '/sys/class/net/".$in_iface."/statistics/rx_bytes' ]; +then + echo -n 'rx:'; + cat /sys/class/net/".$in_iface."/statistics/rx_bytes; + echo -n 'tx:'; + cat /sys/class/net/".$in_iface."/statistics/tx_bytes; +else + echo 'rx:0'; + echo 'tx:0'; +fi"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my $transmit_sizes = ""; + if ($is_local) + { + # Local call. + ($transmit_sizes, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:transmit_sizes' => $transmit_sizes, + 's2:return_code' => $return_code, + }}); + } + else + { + # Remote call + ($transmit_sizes, my $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + user => $remote_user, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:transmit_sizes' => $transmit_sizes, + 's2:error' => $error, + 's3:return_code' => $return_code, + }}); + } + foreach my $line (split/\n/, $transmit_sizes) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /rx:(\d+)/) + { + $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes} = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${in_iface}::rx_bytes" => $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes}." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes}}).")", + }}); + } + if ($line =~ /tx:(\d+)/) + { + $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes} = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${in_iface}::tx_bytes" => $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes}." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes}}).")", + }}); + } + } + } + } + next if not $in_iface; + if ($in_iface eq "lo") + { + # We don't care about 'lo'. + delete $anvil->data->{network}{$host}{interface}{$in_iface}; + next; + } + if ($line =~ /inet (.*?)\/(.*?) /) + { + my $ip = $1; + my $cidr = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip => $ip, cidr => $cidr }}); + + my $subnet_mask = $cidr; + if (($cidr =~ /^\d{1,2}$/) && ($cidr >= 0) && ($cidr <= 32)) + { + # Convert to subnet mask + $subnet_mask = $anvil->Convert->cidr({cidr => $cidr}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { subnet_mask => $subnet_mask }}); + } + + $anvil->data->{network}{$host}{interface}{$in_iface}{ip} = $ip; + $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask} = $subnet_mask; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:network::${host}::interface::${in_iface}::ip" => $anvil->data->{network}{$host}{interface}{$in_iface}{ip}, + "s2:network::${host}::interface::${in_iface}::subnet_mask" => $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask}, + }}); + } + if ($line =~ /ether (.*?) /i) + { + my $mac_address = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac_address => $mac_address }}); + + # Wireless interfaces have a 'permaddr' that is stable. The MAC address shown by 'ether' changes constantly, for some odd reason. + if ($line =~ /permaddr (.*)$/) + { + $mac_address = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac_address => $mac_address }}); + } + + $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address} = $mac_address; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${in_iface}::mac_address" => $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}, + }}); + + # If this is a bond or bridge, don't record the MAC address. It confuses things as + # they show the MAC of the active interface. If this is an interface, see if the file + # '/sys/class/net//bonding_slave/perm_hwaddr' exists and, if so, read the MAC + # address from there. If not, read the MAC address from + # '/sys/class/net//address'. + my $shell_call = 'IFACE='.$in_iface.' +if [ -e "/sys/class/net/${IFACE}/bridge" ]; +then + echo bridge; +elif [ -e "/proc/net/bonding/${IFACE}" ]; +then + echo bond; +else + ethtool -P ${IFACE} +fi'; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + if ($is_local) + { + # Local call. + ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, + }}); + } + else + { + # Remote call + ($output, my $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + user => $remote_user, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:error' => $error, + 's3:return_code' => $return_code, + }}); + } + foreach my $line (split/\n/, $output) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /^.*: (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)$/) + { + my $real_mac = $1; + $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address} = $real_mac; + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${in_iface}::mac_address" => $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}, + }}); + + # Make it easy to look up an interface name based on a given MAC + # address. + $anvil->data->{network}{$host}{mac_address}{$real_mac}{interface} = $in_iface; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::mac_address::${real_mac}::interface" => $anvil->data->{network}{$host}{mac_address}{$real_mac}{interface}, + }}); + } + } + } + if ($line =~ /mtu (\d+) /i) + { + my $mtu = $1; + $anvil->data->{network}{$host}{interface}{$in_iface}{mtu} = $mtu; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${in_iface}::mtu" => $anvil->data->{network}{$host}{interface}{$in_iface}{mtu}, + }}); + } + } + + # Read the config files for the interfaces we've found. Use 'ls' to find the interface files. Then + # we'll read them all in. + $shell_call = $anvil->data->{path}{exe}{ls}." ".$anvil->data->{path}{directories}{ifcfg}; + $output = ""; + if ($is_local) + { + # Local call. + ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, + }}); + } + else + { + # Remote call + ($output, my $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + user => $remote_user, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:error' => $error, + 's3:return_code' => $return_code, + }}); + } + foreach my $line (split/\n/, $output) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + next if $line !~ /^ifcfg-/; + + my $full_path = $anvil->data->{path}{directories}{ifcfg}."/".$line; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { full_path => $full_path }}); + + my $file_body = $anvil->Storage->read_file({ + debug => $debug, + file => $full_path, + target => $target, + password => $password, + port => $port, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:full_path" => $full_path, + "s2:file_body" => $file_body, + }}); + + # Break it apart and store any variables. + my $temp = {}; + my $interface = ""; + foreach my $line (split/\n/, $file_body) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + next if $line =~ /^#/; + if ($line =~ /(.*?)=(.*)/) + { + my $variable = $1; + my $value = $2; + $value =~ s/^"(.*)"$/$1/; + $temp->{$variable} = $value; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "temp->{$variable}" => $temp->{$variable} }}); + + if (uc($variable) eq "DEVICE") + { + # If this isn't a device we saw in 'ip addr', skip it by just not setting the interface variable + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { value => $value }}); + last if not exists $anvil->data->{network}{$host}{interface}{$value}; + + $interface = $value; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { interface => $interface }}); + } + } + + if ($interface) + { + $anvil->data->{network}{$host}{interface}{$interface}{file} = $full_path; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${interface}::file" => $anvil->data->{network}{$host}{interface}{$interface}{file}, + }}); + foreach my $variable (sort {$a cmp $b} keys %{$temp}) + { + $anvil->data->{network}{$host}{interface}{$interface}{variable}{$variable} = $temp->{$variable}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${interface}::file::variable::${variable}" => $anvil->data->{network}{$host}{interface}{$interface}{variable}{$variable}, + }}); + } + } + } + } + + # Get the routing info. + my $lowest_metric = 99999999; + my $route_interface = ""; + my $route_ip = ""; + $shell_call = $anvil->data->{path}{exe}{ip}." route show"; + $output = ""; + if ($is_local) + { + # Local call. + ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, + }}); + } + else + { + # Remote call + ($output, my $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + user => $remote_user, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:error' => $error, + 's3:return_code' => $return_code, + }}); + } + foreach my $line (split/\n/, $output) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /default via (.*?) dev (.*?) proto .*? metric (\d+)/i) + { + my $this_ip = $1; + my $this_interface = $2; + my $metric = $3; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:this_ip' => $this_ip, + 's2:this_interface' => $this_interface, + 's3:metric' => $metric, + 's4:lowest_metric' => $lowest_metric, + }}); + + if ($metric < $lowest_metric) + { + $lowest_metric = $metric; + $route_interface = $this_interface; + $route_ip = $this_ip; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + lowest_metric => $lowest_metric, + route_interface => $route_interface, + route_ip => $route_ip, + }}); + } + } + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + route_interface => $route_interface, + route_ip => $route_ip, + }}); + + # If I got a route, get the DNS. + if ($route_interface) + { + # I want to build the DNS list from only the interface that is used for routing. + my $in_interface = ""; + my $dns_list = ""; + my $dns_hash = {}; + my $shell_call = $anvil->data->{path}{exe}{nmcli}." dev show"; + my $output = ""; + if ($is_local) + { + # Local call. + ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, + }}); + } + else + { + # Remote call + ($output, my $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + user => $remote_user, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:error' => $error, + 's3:return_code' => $return_code, + }}); + } + foreach my $line (split/\n/, $output) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /GENERAL.DEVICE:\s+(.*)$/) + { + $in_interface = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { in_interface => $in_interface }}); + } + if (not $line) + { + $in_interface = ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { in_interface => $in_interface }}); + } + + next if $in_interface ne $route_interface; + + if ($line =~ /IP4.DNS\[(\d+)\]:\s+(.*)/i) + { + my $order = $1; + my $ip = $2; + + $dns_hash->{$order} = $ip; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "dns_hash->{$order}" => $dns_hash->{$order} }}); + } + } + + foreach my $order (sort {$a cmp $b} keys %{$dns_hash}) + { + $dns_list .= $dns_hash->{$order}.", "; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:dns_hash->{$order}" => $dns_hash->{$order}, + "s2:dns_list" => $dns_list, + }}); + } + $dns_list =~ s/, $//; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { dns_list => $dns_list }}); + + $anvil->data->{network}{$host}{interface}{$route_interface}{default_gateway} = 1; + $anvil->data->{network}{$host}{interface}{$route_interface}{gateway} = $route_ip; + $anvil->data->{network}{$host}{interface}{$route_interface}{dns} = $dns_list; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${route_interface}::default_gateway" => $anvil->data->{network}{$host}{interface}{$route_interface}{default_gateway}, + "network::${host}::interface::${route_interface}::gateway" => $anvil->data->{network}{$host}{interface}{$route_interface}{gateway}, + "network::${host}::interface::${route_interface}::dns" => $anvil->data->{network}{$host}{interface}{$route_interface}{dns}, + }}); + } + + return(0); +} + +=head2 get_network + +This takes an IP address and subnet and returns the network it belongs too. For example; + + my $network = $anvil->Network->get_network({ip => "10.2.4.1", subnet_mask => "255.255.0.0"}); + +This would set C<< $network >> to C<< 10.2.0.0 >>. + +If the network can't be caluclated for any reason, and empty string will be returned. + +Parameters; + +=head3 ip (required) + +This is the IPv4 IP address being calculated. + +=head3 subnet_mask (required) + +This is the subnet mask of the IP address being calculated. + +=cut +sub get_network +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_network()" }}); + + my $network = ""; + my $ip = defined $parameter->{ip} ? $parameter->{ip} : ""; + my $subnet_mask = defined $parameter->{subnet_mask} ? $parameter->{subnet_mask} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + ip => $ip, + subnet_mask => $subnet_mask, + }}); + + if (not $ip) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->get_network()", parameter => "ip" }}); + return(""); + } + if (not $subnet_mask) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->get_network()", parameter => "subnet_mask" }}); + return(""); + } + + my $block = Net::Netmask->new($ip."/".$subnet_mask); + my $base = $block->base(); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { base => $base }}); + + if ($anvil->Validate->ipv4({ip => $base})) + { + $network = $base; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { network => $network }}); + } + + return($network); +} + + +=head2 is_local + +This method takes a host name or IP address and looks to see if it matches the local system. If it does, it returns C<< 1 >>. Otherwise it returns C<< 0 >>. + +Parameters; + +=head3 host (required) + +This is the host name (or IP address) to check against the local system. + +=cut +### NOTE: Do not log in here, it will cause a recursive loop! +sub is_local +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + + my $host = $parameter->{host} ? $parameter->{host} : ""; + return(1) if not $host; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + host => $host, + }}); + + # If we've checked this host before, return the cached answer + if (exists $anvil->data->{cache}{is_local}{$host}) + { + return($anvil->data->{cache}{is_local}{$host}); + } + + $anvil->data->{cache}{is_local}{$host} = 0; + if (($host eq $anvil->Get->host_name) or + ($host eq $anvil->Get->short_host_name) or + ($host eq "localhost") or + ($host eq "127.0.0.1")) + { + # It's local + $anvil->data->{cache}{is_local}{$host} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "cache::is_local::${host}" => $anvil->data->{cache}{is_local}{$host} }}); + } + else + { + # Get the list of current IPs and see if they match. + my $local_host = $anvil->Get->short_host_name(); + if (not exists $anvil->data->{network}{$local_host}{interface}) + { + $anvil->Network->get_ips({debug => 9999}); + } + foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$local_host}{interface}}) + { + next if not defined $anvil->data->{network}{$local_host}{interface}{$interface}{ip}; + if ($host eq $anvil->data->{network}{$local_host}{interface}{$interface}{ip}) + { + $anvil->data->{cache}{is_local}{$host} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "cache::is_local::${host}" => $anvil->data->{cache}{is_local}{$host} }}); + last; + } + } + } + + #$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { is_local => $is_local }}); + return($anvil->data->{cache}{is_local}{$host}); +} + + +=head2 is_our_interface + +This method takes an interface name and returns C<< 1 >> if the interface is one of the ones we manage (A C<< BCN >>, C<< IFN >>, C<< SN >> or C<< MN >> interface). If not, C<< 0 >> is returned. + +Parameters; + +=head3 interface (required) + +This is the name of the interface being evaluated. + +=cut +sub is_our_interface +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->is_our_interface()" }}); + + my $interface = $parameter->{interface} ? $parameter->{interface} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + interface => $interface, + }}); + + if (not $interface) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_our_interface()", parameter => "interface" }}); + return(0); + } + + my $ours = 0; + if (($interface =~ /^bcn/i) or + ($interface =~ /^sn/i) or + ($interface =~ /^ifn/i) or + ($interface =~ /^mn/i)) + { + $ours = 1; + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ours => $ours }}); + return($ours); +} + +=head2 is_ip_in_network + +This takes an IP address, along with network and subnet mask and sees if the IP address is within the network. If it is, it returns C<< 1 >>. If the IP address doesn't match the network, C<< 0 >> is returned. + +Parameters + +=head3 ip (required) + +This is the ip IP address being analyzed. + +=head3 network (required) + +This is the IP address that will be paired with the subnet mask to see if the ip matches. + +=head3 subnet_mask (required) + +This is the subnet mask paired against the IP address used to check the ip against. + +=cut +sub is_ip_in_network +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->is_ip_in_network()" }}); + + my $ip = defined $parameter->{ip} ? $parameter->{ip} : ""; + my $network = defined $parameter->{network} ? $parameter->{network} : ""; + my $subnet_mask = defined $parameter->{subnet_mask} ? $parameter->{subnet_mask} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + ip => $ip, + network => $network, + subnet_mask => $subnet_mask, + }}); + + if (not $network) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_ip_in_network()", parameter => "network" }}); + return(0); + } + elsif (not $anvil->Validate->ipv4({ip => $network})) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "warning_0019", variables => { parameter => "network", network => $network }}); + return(0); + } + if (not $ip) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_ip_in_network()", parameter => "ip" }}); + return(0); + } + elsif (not $anvil->Validate->ipv4({ip => $ip})) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "warning_0019", variables => { parameter => "ip", network => $ip }}); + return(0); + } + if (not $subnet_mask) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_ip_in_network()", parameter => "subnet_mask" }}); + return(0); + } + elsif (not $anvil->Validate->subnet_mask({subnet_mask => $subnet_mask})) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "warning_0020", variables => { parameter => "subnet_mask", subnet_mask => $subnet_mask }}); + return(0); + } + + my $match = 0; + my $block = Net::Netmask->new($network."/".$subnet_mask); + if ($block->match($ip)) + { + # This is a match! + $match = 1; + } + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { match => $match }}); + return($match); +} + + +=head2 load_interfces + +This loads all network information for the given host UUID. + +The main difference from C<< ->load_ips() >> is that this method loads information about all interfaces, regardless of if they have an IP, as well as their link state and link information. + +The loaded data will be stored as: + +* C<< machine::::interface:::: + +Parameters; + +=head3 clear (optional, default '1') + +When set, any previously known information is cleared. Specifically, the C<< network::> >> hash is deleted prior to the load. To prevent this, set this to C<< 0 >>. + +=head3 host (optional, default is 'host_uuid' value) + +This is the optional C<< target >> string to use in the hash where the data is stored. + +=head3 host_uuid (optional, default 'sys::host_uuid') + +This is the C<< host_uuid >> of the hosts whose IP and interface data that you want to load. The default is to load the local machine's data. + +=cut +sub load_interfces +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->load_interfces()" }}); + + my $clear = defined $parameter->{clear} ? $parameter->{clear} : 1; + my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : $anvil->data->{sys}{host_uuid}; + my $host = defined $parameter->{host} ? $parameter->{host} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + clear => $clear, + host => $host, + host_uuid => $host_uuid, + }}); + + if (not $host_uuid) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->load_interfces()", parameter => "host_uuid" }}); + return(""); + } + + if (not $host) + { + $host = $host_uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); + } + + if (($clear) && (exists $anvil->data->{network}{$host})) + { + delete $anvil->data->{network}{$host}; + } + + # Now load bridge info + my $query = " +SELECT + bridge_uuid, + bridge_name, + bridge_id, + bridge_mac_address, + bridge_mtu, + bridge_stp_enabled +FROM + bridges +WHERE + bridge_id != 'DELETED' +AND + bridge_host_uuid = ".$anvil->Database->quote($host_uuid)." +;"; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + results => $results, + count => $count, + }}); + foreach my $row (@{$results}) + { + my $bridge_uuid = defined $row->[0] ? $row->[0] : ""; + my $bridge_name = defined $row->[1] ? $row->[1] : ""; + my $bridge_id = defined $row->[2] ? $row->[2] : ""; + my $bridge_mac_address = defined $row->[3] ? $row->[3] : ""; + my $bridge_mtu = defined $row->[4] ? $row->[4] : ""; + my $bridge_stp_enabled = defined $row->[5] ? $row->[5] : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + bridge_uuid => $bridge_uuid, + bridge_name => $bridge_name, + bridge_id => $bridge_id, + bridge_mac_address => $bridge_mac_address, + bridge_mtu => $bridge_mtu, + bridge_stp_enabled => $bridge_stp_enabled, + }}); + + # Record the bridge_uuid -> name + $anvil->data->{network}{$host}{bridge_uuid}{$bridge_uuid}{name} = $bridge_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::bridge_uuid::${bridge_uuid}::name" => $anvil->data->{network}{$host}{bridge_uuid}{$bridge_uuid}{name}, + }}); + + # We'll initially load empty strings for what would be the IP information. Any interface with IPs will be populated when we call + $anvil->data->{network}{$host}{interface}{$bridge_name}{uuid} = $bridge_uuid; + $anvil->data->{network}{$host}{interface}{$bridge_name}{id} = $bridge_id; + $anvil->data->{network}{$host}{interface}{$bridge_name}{mac_address} = $bridge_mac_address; + $anvil->data->{network}{$host}{interface}{$bridge_name}{mtu} = $bridge_mtu; + $anvil->data->{network}{$host}{interface}{$bridge_name}{stp_enabled} = $bridge_stp_enabled; + $anvil->data->{network}{$host}{interface}{$bridge_name}{type} = "bridge"; + $anvil->data->{network}{$host}{interface}{$bridge_name}{interfaces} = []; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${bridge_name}::uuid" => $anvil->data->{network}{$host}{interface}{$bridge_name}{uuid}, + "network::${host}::interface::${bridge_name}::id" => $anvil->data->{network}{$host}{interface}{$bridge_name}{id}, + "network::${host}::interface::${bridge_name}::mac_address" => $anvil->data->{network}{$host}{interface}{$bridge_name}{mac_address}, + "network::${host}::interface::${bridge_name}::mtu" => $anvil->data->{network}{$host}{interface}{$bridge_name}{mtu}, + "network::${host}::interface::${bridge_name}::stp_enabled" => $anvil->data->{network}{$host}{interface}{$bridge_name}{stp_enabled}, + "network::${host}::interface::${bridge_name}::type" => $anvil->data->{network}{$host}{interface}{$bridge_name}{type}, + }}); + } + + # Now load bond info + $query = " +SELECT + bond_uuid, + bond_name, + bond_mode, + bond_mtu, + bond_primary_interface, + bond_primary_reselect, + bond_active_interface, + bond_mii_polling_interval, + bond_up_delay, + bond_down_delay, + bond_mac_address, + bond_operational, + bond_bridge_uuid +FROM + bonds WHERE bond_mode != 'DELETED' +AND + bond_host_uuid = ".$anvil->Database->quote($host_uuid)." +;"; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); + $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + results => $results, + count => $count, + }}); + foreach my $row (@{$results}) + { + my $bond_uuid = defined $row->[0] ? $row->[0] : ""; + my $bond_name = defined $row->[1] ? $row->[1] : ""; + my $bond_mode = defined $row->[2] ? $row->[2] : ""; + my $bond_mtu = defined $row->[3] ? $row->[3] : ""; + my $bond_primary_interface = defined $row->[4] ? $row->[4] : ""; + my $bond_primary_reselect = defined $row->[5] ? $row->[5] : ""; + my $bond_active_interface = defined $row->[6] ? $row->[6] : ""; + my $bond_mii_polling_interval = defined $row->[7] ? $row->[7] : ""; + my $bond_up_delay = defined $row->[8] ? $row->[8] : ""; + my $bond_down_delay = defined $row->[9] ? $row->[9] : ""; + my $bond_mac_address = defined $row->[10] ? $row->[10] : ""; + my $bond_operational = defined $row->[11] ? $row->[11] : ""; + my $bond_bridge_uuid = defined $row->[12] ? $row->[12] : ""; + my $bridge_name = ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + bond_uuid => $bond_uuid, + bond_name => $bond_name, + bond_mode => $bond_mode, + bond_mtu => $bond_mtu, + bond_primary_interface => $bond_primary_interface, + bond_primary_reselect => $bond_primary_reselect, + bond_active_interface => $bond_active_interface, + bond_mii_polling_interval => $bond_mii_polling_interval, + bond_up_delay => $bond_up_delay, + bond_down_delay => $bond_down_delay, + bond_mac_address => $bond_mac_address, + bond_operational => $bond_operational, + bond_bridge_uuid => $bond_bridge_uuid, + }}); + + # If this bond is connected to a bridge, get the bridge name. + if (($bond_bridge_uuid) && (defined $anvil->data->{network}{$host}{bridge_uuid}{$bond_bridge_uuid}{name})) + { + $bridge_name = $anvil->data->{network}{$host}{bridge_uuid}{$bond_bridge_uuid}{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + bond_bridge_uuid => $bond_bridge_uuid, + bridge_name => $bridge_name, + }}); + push @{$anvil->data->{network}{$host}{interface}{$bridge_name}{interfaces}}, $bond_name; + } + + # Record the bond_uuid -> name + $anvil->data->{network}{$host}{bond_uuid}{$bond_uuid}{name} = $bond_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::bond_uuid::${bond_uuid}::name" => $anvil->data->{network}{$host}{bond_uuid}{$bond_uuid}{name}, + }}); + + # We'll initially load empty strings for what would be the IP information. Any interface with IPs will be populated when we call + $anvil->data->{network}{$host}{interface}{$bond_name}{uuid} = $bond_uuid; + $anvil->data->{network}{$host}{interface}{$bond_name}{mode} = $bond_mode; + $anvil->data->{network}{$host}{interface}{$bond_name}{mtu} = $bond_mtu; + $anvil->data->{network}{$host}{interface}{$bond_name}{primary_interface} = $bond_primary_interface; + $anvil->data->{network}{$host}{interface}{$bond_name}{primary_reselect} = $bond_primary_reselect; + $anvil->data->{network}{$host}{interface}{$bond_name}{active_interface} = $bond_active_interface; + $anvil->data->{network}{$host}{interface}{$bond_name}{mii_polling_interval} = $bond_mii_polling_interval; + $anvil->data->{network}{$host}{interface}{$bond_name}{up_delay} = $bond_up_delay; + $anvil->data->{network}{$host}{interface}{$bond_name}{down_delay} = $bond_down_delay; + $anvil->data->{network}{$host}{interface}{$bond_name}{mac_address} = $bond_mac_address; + $anvil->data->{network}{$host}{interface}{$bond_name}{operational} = $bond_operational; + $anvil->data->{network}{$host}{interface}{$bond_name}{bridge_uuid} = $bond_bridge_uuid; + $anvil->data->{network}{$host}{interface}{$bond_name}{type} = "bond"; + $anvil->data->{network}{$host}{interface}{$bond_name}{interfaces} = []; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${bond_name}::uuid" => $anvil->data->{network}{$host}{interface}{$bond_name}{uuid}, + "network::${host}::interface::${bond_name}::mode" => $anvil->data->{network}{$host}{interface}{$bond_name}{mode}, + "network::${host}::interface::${bond_name}::mtu" => $anvil->data->{network}{$host}{interface}{$bond_name}{mtu}, + "network::${host}::interface::${bond_name}::primary_interface" => $anvil->data->{network}{$host}{interface}{$bond_name}{primary_interface}, + "network::${host}::interface::${bond_name}::primary_reselect" => $anvil->data->{network}{$host}{interface}{$bond_name}{primary_reselect}, + "network::${host}::interface::${bond_name}::active_interface" => $anvil->data->{network}{$host}{interface}{$bond_name}{active_interface}, + "network::${host}::interface::${bond_name}::mii_polling_interval" => $anvil->data->{network}{$host}{interface}{$bond_name}{mii_polling_interval}, + "network::${host}::interface::${bond_name}::up_delay" => $anvil->data->{network}{$host}{interface}{$bond_name}{up_delay}, + "network::${host}::interface::${bond_name}::down_delay" => $anvil->data->{network}{$host}{interface}{$bond_name}{down_delay}, + "network::${host}::interface::${bond_name}::mac_address" => $anvil->data->{network}{$host}{interface}{$bond_name}{mac_address}, + "network::${host}::interface::${bond_name}::operational" => $anvil->data->{network}{$host}{interface}{$bond_name}{operational}, + "network::${host}::interface::${bond_name}::bridge_uuid" => $anvil->data->{network}{$host}{interface}{$bond_name}{bridge}, + "network::${host}::interface::${bond_name}::type" => $anvil->data->{network}{$host}{interface}{$bond_name}{type}, + }}); + } + + # The order will allow us to show the order in which the interfaces were changed, which the user can + # use to track interfaces as they unplug and plug cables back in. + my $order = 1; + $query = " +SELECT + network_interface_uuid, + network_interface_mac_address, + network_interface_name, + network_interface_speed, + network_interface_mtu, + network_interface_link_state, + network_interface_operational, + network_interface_duplex, + network_interface_medium, + network_interface_bond_uuid, + network_interface_bridge_uuid +FROM + network_interfaces +WHERE + network_interface_operational != 'DELETED' +AND + network_interface_host_uuid = ".$anvil->Database->quote($host_uuid)." +ORDER BY + modified_date DESC +;"; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); + $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + results => $results, + count => $count, + }}); + my $changed_order = 1; + foreach my $row (@{$results}) + { + my $network_interface_uuid = defined $row->[0] ? $row->[0] : ""; + my $network_interface_mac_address = defined $row->[1] ? $row->[1] : ""; + my $network_interface_name = defined $row->[2] ? $row->[2] : ""; + my $network_interface_speed = defined $row->[3] ? $row->[3] : ""; + my $network_interface_mtu = defined $row->[4] ? $row->[4] : ""; + my $network_interface_link_state = defined $row->[5] ? $row->[5] : ""; + my $network_interface_operational = defined $row->[6] ? $row->[6] : ""; + my $network_interface_duplex = defined $row->[7] ? $row->[7] : ""; + my $network_interface_medium = defined $row->[8] ? $row->[8] : ""; + my $network_interface_bond_uuid = defined $row->[9] ? $row->[9] : ""; + my $network_interface_bridge_uuid = defined $row->[10] ? $row->[10] : ""; + my $bond_name = ""; + my $bridge_name = ""; + my $this_change_orger = 0; + if (($network_interface_name =~ /^virbr/) or ($network_interface_name =~ /^vnet/)) + { + # This isn't a physical NIC, so it doesn't get a changed order + } + else + { + $this_change_orger = $changed_order++; + } + if (($network_interface_bond_uuid) && (defined $anvil->data->{network}{$host}{bond_uuid}{$network_interface_bond_uuid}{name})) + { + $bond_name = $anvil->data->{network}{$host}{bond_uuid}{$network_interface_bond_uuid}{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + network_interface_name => $network_interface_name, + bond_name => $bond_name, + }}); + push @{$anvil->data->{network}{$host}{interface}{$bond_name}{interfaces}}, $network_interface_name; + } + if (($network_interface_bridge_uuid) && (defined $anvil->data->{network}{$host}{bridge_uuid}{$network_interface_bridge_uuid}{name})) + { + $bridge_name = $anvil->data->{network}{$host}{bridge_uuid}{$network_interface_bridge_uuid}{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + network_interface_name => $network_interface_name, + bridge_name => $bridge_name, + }}); + push @{$anvil->data->{network}{$host}{interface}{$bridge_name}{interfaces}}, $network_interface_name; + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + network_interface_uuid => $network_interface_uuid, + network_interface_mac_address => $network_interface_mac_address, + network_interface_name => $network_interface_name, + network_interface_speed => $network_interface_speed, + network_interface_mtu => $network_interface_mtu, + network_interface_link_state => $network_interface_link_state, + network_interface_operational => $network_interface_operational, + network_interface_duplex => $network_interface_duplex, + network_interface_medium => $network_interface_medium, + network_interface_bond_uuid => $network_interface_bond_uuid, + network_interface_bridge_uuid => $network_interface_bridge_uuid, + bond_name => $bond_name, + changed_order => $this_change_orger, + }}); + + # We'll initially load empty strings for what would be the IP information. Any interface with IPs will be populated when we call + $anvil->data->{network}{$host}{interface}{$network_interface_name}{uuid} = $network_interface_uuid; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{mac_address} = $network_interface_mac_address; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{speed} = $network_interface_speed; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{mtu} = $network_interface_mtu; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{link_state} = $network_interface_link_state; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{operational} = $network_interface_operational; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{duplex} = $network_interface_duplex; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{medium} = $network_interface_medium; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_uuid} = $network_interface_bond_uuid; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_name} = $bond_name; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_uuid} = $network_interface_bridge_uuid; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_name} = $bridge_name; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{type} = "interface"; + $anvil->data->{network}{$host}{interface}{$network_interface_name}{changed_order} = $this_change_orger; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "network::${host}::interface::${network_interface_name}::uuid" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{uuid}, + "network::${host}::interface::${network_interface_name}::mac_address" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{mac_address}, + "network::${host}::interface::${network_interface_name}::speed" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{speed}, + "network::${host}::interface::${network_interface_name}::mtu" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{mtu}, + "network::${host}::interface::${network_interface_name}::link_state" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{link_state}, + "network::${host}::interface::${network_interface_name}::operational" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{operational}, + "network::${host}::interface::${network_interface_name}::duplex" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{duplex}, + "network::${host}::interface::${network_interface_name}::medium" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{medium}, + "network::${host}::interface::${network_interface_name}::bond_uuid" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_uuid}, + "network::${host}::interface::${network_interface_name}::bond_name" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bond_name}, + "network::${host}::interface::${network_interface_name}::bridge_uuid" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_uuid}, + "network::${host}::interface::${network_interface_name}::bridge_name" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{bridge_name}, + "network::${host}::interface::${network_interface_name}::type" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{type}, + "network::${host}::interface::${network_interface_name}::changed_order" => $anvil->data->{network}{$host}{interface}{$network_interface_name}{changed_order}, + }}); + } + + # Load the IPs + $anvil->Network->load_ips({ + debug => $debug, + host_uuid => $host_uuid, + host => $host, + clear => 0, + }); + + return(0); +} + + +=head2 load_ips + +This method loads and stores the same data as the C<< get_ips >> method, but does so by loading data from the database, instead of collecting it directly from the host. As such, it can also be used by C<< find_matches >>. + +C<< Note >>: IP addresses that have been deleted will be marked so by C<< ip >> being set to C<< DELETED >>. + +The loaded data will be stored as: + +* C<< network::::interface::::ip >> - If an IP address is set +* C<< network::::interface::::subnet_mask >> - If an IP is set +* C<< network::::interface::::mac >> - Always set. +* C<< network::::interface::::default_gateway >> = C<< 0 >> if not the default gateway, C<< 1 >> if so. +* C<< network::::interface::::gateway >> = If the default gateway, this is the gateway IP address. +* C<< network::::interface::::dns >> = If the default gateway, this is the comma-separated list of active DNS servers. + +Parameters; + +=head3 clear (optional, default '1') + +When set, any previously known information is cleared. Specifically, the C<< network::> >> hash is deleted prior to the load. To prevent this, set this to C<< 0 >>. + +=head3 host (optional, default is 'host_uuid' value) + +This is the optional C<< host >> string to use in the hash where the data is stored. + +=head3 host_uuid (optional, default 'sys::host_uuid') + +This is the C<< host_uuid >> of the hosts whose IP and interface data that you want to load. The default is to load the local machine's data. + +=cut +sub load_ips +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->load_ips()" }}); + + my $clear = defined $parameter->{clear} ? $parameter->{clear} : 1; + my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : $anvil->Get->host_uuid; + my $host = defined $parameter->{host} ? $parameter->{host} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + clear => $clear, + host => $host, + host_uuid => $host_uuid, + }}); + + if (not $host_uuid) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->load_ips()", parameter => "ip" }}); + return(""); + } + + if (not $host) + { + $host = $host_uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); + } + + if (($clear) && (exists $anvil->data->{network}{$host})) + { + delete $anvil->data->{network}{$host}; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0700", variables => { hash => "network::${host}" }}); + } + + # Read in all IPs, so that we know which to remove. my $query = " SELECT ip_address_address, @@ -1918,532 +2912,720 @@ AND return(0); } -=head2 get_company_from_mac - -This takes a MAC address (or the first six bytes) and returns the company that owns the OUI. If the company name is not found, an expty string is returned. - -Parameters; - -=head3 mac (required) - -This is the first six bytes of the mac address, C<< xx:xx:xx >> format, being searched for. - -=cut -sub get_company_from_mac -{ - my $self = shift; - my $parameter = shift; - my $anvil = $self->parent; - my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_company_from_mac()" }}); - - my $mac = defined $parameter->{mac} ? lc($parameter->{mac}) : ""; - my $company = ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - mac => $mac, - }}); - - if (not $mac) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->get_company_from_mac()", parameter => "mac_prefix" }}); - return(""); - } - - # Have I already looked this one up? - if ($anvil->data->{cache}{mac_to_oui}{$mac}) - { - # Yup, no need to process. - return($anvil->data->{cache}{mac_to_oui}{$mac}); - } - - my $valid_mac = $anvil->Validate->mac({mac => $mac}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac => $mac }}); - if ($valid_mac) - { - # Strip the first six bytes. - $mac = ($mac =~ /^([0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2})/i)[0]; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac => $mac }}); - } - elsif ($mac !~ /[0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}/i) - { - # Bad format - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0104", variables => { mac => $mac }}); - return(""); - } - - my $query = "SELECT oui_company_name FROM oui WHERE oui_mac_prefix = ".$anvil->Database->quote(lc($mac)).";"; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); - my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); - my $count = @{$results}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - results => $results, - count => $count, - }}); - if ($count) - { - $company = $results->[0]->[0]; - $anvil->data->{cache}{mac_to_oui}{$mac} = $company; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { company => $company }}); - } - - return($company); -} - - -=head2 get_ip_from_mac - -This takes a MAC address and tries to convert it to an IP address. If no IP is found, an empty string is returned. - -Parameters; - -=head3 mac (required) - -This is the MAC address we're looking for an IP to match to. The format must be C<< aa:bb:cc:dd:ee:ff >>. - -=cut -sub get_ip_from_mac -{ - my $self = shift; - my $parameter = shift; - my $anvil = $self->parent; - my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_ip_from_mac()" }}); - - my $ip = ""; - my $mac = defined $parameter->{mac} ? $parameter->{mac} : ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - mac => $mac, - }}); - - my $query = "SELECT mac_to_ip_ip_address FROM mac_to_ip WHERE mac_to_ip_mac_address = ".$anvil->Database->quote(lc($mac)).";"; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0124", variables => { query => $query }}); - my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); - my $count = @{$results}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - results => $results, - count => $count, - }}); - if ($count) - { - $ip = $results->[0]->[0]; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip => $ip }}); - } - - return($ip); -} - -=head2 get_ips -This method checks the local system for interfaces and stores them in: +=head2 manage_firewall -* C<< network::::interface::::ip >> - If an IP address is set -* C<< network::::interface::::subnet_mask >> - If an IP is set -* C<< network::::interface::::mac_address >> - Always set. -* C<< network::::interface::::mtu >> - Always set. -* C<< network::::interface::::default_gateway >> = C<< 0 >> if not the default gateway, C<< 1 >> if so. -* C<< network::::interface::::gateway >> = If the default gateway, this is the gateway IP address. -* C<< network::::interface::::dns >> = If the default gateway, this is the comma-separated list of active DNS servers. +B<< NOTE >>: So far, only C<< check >> is implemented. -To make it convenient to translate a MAC address into interface names, this hash is also stored; +This method manages a C<< firewalld >> firewall. -* C<< network::::mac_address::::interface = Interface name. +If no parameters are passed, it works by determining what should be open, making sure those things are open, and closing anything open that shouldn't be. -When called without a C<< target >>, C<< local >> is used. +If the firewall is off, C<< 1 >> is returned. Otherwise C<< 0 >> is returned, unless there was an error in which case C<< !!error!! >> is returned. -To aid in look-up by MAC address, C<< network::mac_address::::iface >> is also set. Note that this is not target-dependent. +When called with C<< task = check >>, and if a port is specified, then C<< 1 >> will be returned if the port is open and C<< 0 >> if it is closed. Parameters; -=head3 password (optional) +=head3 task (optional, default 'check') -If C<< target >> is set, this is the password used to log into the remote system as the C<< remote_user >>. If it is not set, an attempt to connect without a password will be made (though this will usually fail). +If set to C<< open >>, it will open the corresponding C<< port >>. If set to C<< close >>, it will close the corresponding C<< port >>. If set to c<< check >>, then it depends on is a port is given. If not, the full configuration is checked, and updated to the firewall are made as needed. If a port (and optionally zone and/or protocol) is specified, that specific request is checked. -=head3 port (optional, default 22) +The default is C<< all >>, which checks the entire configuration, updating the active configuration as needed. -If C<< target >> is set, this is the TCP port number used to connect to the remote machine. +=head3 port_number (required) -=head3 remote_user (optional) +This is the port number to work on. -If C<< target >> is set, this is the user account that will be used when connecting to the remote system. +If not specified, C<< service >> is required. -=head3 target (optional) +=head3 protocol (optional, required if 'port' set) -If set, the file will be read from the target machine. This must be either an IP address or a resolvable host name. +This can be c<< tcp >> or C<< upd >> and is used to specify what protocol to use with the C<< port >>, when specified. Multiple protocols can be defined using comma-separated list. Example, C<< tcp,udp >>. -The file will be copied to the local system using C<< $anvil->Storage->rsync() >> and stored in C<< /tmp/. >>. if C<< cache >> is set, the file will be preserved locally. Otherwise it will be deleted once it has been read into memory. +=head3 zone (optional) -B<< Note >>: the temporary file will be prefixed with the path to the file name, with the C<< / >> converted to C<< _ >>. +If set to a zone name, the check/change is performed against the specific zone. Multiple zones can be specified using comma-separated list. Example, C<< BCN1 >>, or C<< BCN1,IFN1 >>. =cut -sub get_ips +sub manage_firewall { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_ips()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->manage_firewall()" }}); - my $password = defined $parameter->{password} ? $parameter->{password} : ""; - my $port = defined $parameter->{port} ? $parameter->{port} : 22; - my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; - my $target = defined $parameter->{target} ? $parameter->{target} : ""; + my $task = defined $parameter->{task} ? $parameter->{task} : "check"; + my $port_number = defined $parameter->{port_number} ? $parameter->{port_number} : ""; + my $protocol = defined $parameter->{protocol} ? $parameter->{protocol} : ""; + my $zone = defined $parameter->{zone} ? $parameter->{zone} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - password => $anvil->Log->is_secure($password), - port => $port, - remote_user => $remote_user, - target => $target, + task => $task, + port_number => $port_number, + protocol => $protocol, + zone => $zone }}); - # This is used in the hash reference when storing the data. - my $host = $target ? $target : $anvil->Get->short_host_name(); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); - - if (exists $anvil->data->{network}{$host}) + # Before we do anything, is the firewall even running? + my $firewalld_running = $anvil->Network->check_firewall({debug => $debug}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { firewalld_running => $firewalld_running }}); + if (not $firewalld_running) { - delete $anvil->data->{network}{$host}; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0669"}); + return(1); } - # Reading locally or remote? - my $in_iface = ""; - my $shell_call = $anvil->data->{path}{exe}{ip}." addr list"; - my $output = ""; - my $is_local = $anvil->Network->is_local({host => $target}); - if ($is_local) - { - # Local call. - ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:return_code' => $return_code, - }}); - } - else + # What we do next depends on what we're doing. + my $host_type = $anvil->Get->host_type; + my $host_name = $anvil->Get->short_host_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + host_type => $host_type, + host_name => $host_name, + }}); + $anvil->Network->get_ips({target => $host_name}); + if (($task eq "check") && ($port_number eq "")) { - # Remote call - ($output, my $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - user => $remote_user, - password => $password, - remote_user => $remote_user, - }); + ### Check everything. + my $reload = 0; + + # Check the base firewalld config. + my $changes = $anvil->Network->_check_firewalld_conf({debug => $debug}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + if ($changes eq "1") + { + $reload = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + } + + # Get a list of zones and the interfaces already in them. + $anvil->Network->_get_existing_zone_interfaces({debug => $debug}); + + # What zones do we need, and what zones do we have? + foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$host_name}{interface}}) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { interface => $interface}}); + next if not $anvil->data->{network}{$host_name}{interface}{$interface}{ip}; + my $ip_address = $anvil->data->{network}{$host_name}{interface}{$interface}{ip}; + my $subnet_mask = $anvil->data->{network}{$host_name}{interface}{$interface}{subnet_mask}; + my $default_gateway = $anvil->data->{network}{$host_name}{interface}{$interface}{default_gateway}; + my $zone = uc(($interface =~ /^(.*?)_/)[0]); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + ip_address => $ip_address, + subnet_mask => $subnet_mask, + default_gateway => $default_gateway, + zone => $zone, + }}); + + $anvil->data->{firewalld}{zones}{$zone}{needed} = 1; + $anvil->data->{firewalld}{zones}{$zone}{have} = 0; + $anvil->data->{firewalld}{zones}{$zone}{short_description} = ""; + $anvil->data->{firewalld}{zones}{$zone}{long_description} = ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "firewalld::zones::${zone}::needed" => $anvil->data->{firewalld}{zones}{$zone}{needed}, + }}); + } + + # What zones do we have? $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:error' => $error, - 's3:return_code' => $return_code, + "path::directories::firewalld_zones_etc" => $anvil->data->{path}{directories}{firewalld_zones_etc}, }}); - } - foreach my $line (split/\n/, $output) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line =~ /^\d+: (.*?): /) + local(*DIRECTORY); + opendir(DIRECTORY, $anvil->data->{path}{directories}{firewalld_zones_etc}); + while(my $file = readdir(DIRECTORY)) { - $in_iface = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { in_iface => $in_iface }}); + next if $file !~ /\.xml$/; + my $full_path = $anvil->data->{path}{directories}{firewalld_zones_etc}."/".$file; + $full_path =~ s/\/\//\//g; + my $zone = ($file =~ /(.*)\.xml$/)[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + file => $file, + full_path => $full_path, + zone => $zone, + }}); - $anvil->data->{network}{$host}{interface}{$in_iface}{ip} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{ip}; - $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask}; - $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}; - $anvil->data->{network}{$host}{interface}{$in_iface}{mtu} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{mtu}; - $anvil->data->{network}{$host}{interface}{$in_iface}{default_gateway} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{default_gateway}; - $anvil->data->{network}{$host}{interface}{$in_iface}{gateway} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{gateway}; - $anvil->data->{network}{$host}{interface}{$in_iface}{dns} = "" if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{dns}; - $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes}; - $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes} = 0 if not defined $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes}; + if (not exists $anvil->data->{firewalld}{zones}{$zone}) + { + $anvil->data->{firewalld}{zones}{$zone}{needed} = 0; + } + $anvil->data->{firewalld}{zones}{$zone}{have} = 1; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${in_iface}::ip" => $anvil->data->{network}{$host}{interface}{$in_iface}{ip}, - "network::${host}::interface::${in_iface}::subnet_mask" => $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask}, - "network::${host}::interface::${in_iface}::mac_address" => $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}, - "network::${host}::interface::${in_iface}::mtu" => $anvil->data->{network}{$host}{interface}{$in_iface}{mtu}, - "network::${host}::interface::${in_iface}::default_gateway" => $anvil->data->{network}{$host}{interface}{$in_iface}{default_gateway}, - "network::${host}::interface::${in_iface}::gateway" => $anvil->data->{network}{$host}{interface}{$in_iface}{gateway}, - "network::${host}::interface::${in_iface}::dns" => $anvil->data->{network}{$host}{interface}{$in_iface}{dns}, + "firewalld::zones::${zone}::have" => $anvil->data->{firewalld}{zones}{$zone}{have}, + "firewalld::zones::${zone}::needed" => $anvil->data->{firewalld}{zones}{$zone}{needed}, }}); - if ($in_iface ne "lo") + my $file_body = $anvil->Storage->read_file({ + debug => $debug, + file => $full_path, + force_read => 1, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { file_body => $file_body }}); + local $@; + my $dom = eval { XML::LibXML->load_xml(string => $file_body); }; + if ($@) { - # Read the read and write bytes. - my $read_bytes = 0; - my $write_bytes = 0; - my $shell_call = " -if [ -e '/sys/class/net/".$in_iface."/statistics/rx_bytes' ]; -then - echo -n 'rx:'; - cat /sys/class/net/".$in_iface."/statistics/rx_bytes; - echo -n 'tx:'; - cat /sys/class/net/".$in_iface."/statistics/tx_bytes; -else - echo 'rx:0'; - echo 'tx:0'; -fi"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - my $transmit_sizes = ""; - if ($is_local) + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "warning_0146", variables => { + file => $full_path, + body => $file_body, + error => $@, + }}); + } + else + { + foreach my $element ($dom->findnodes('/zone')) { - # Local call. - ($transmit_sizes, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->data->{firewalld}{zones}{$zone}{short_description} = $element->findvalue('./short') ? $element->findvalue('./short') : "--"; + $anvil->data->{firewalld}{zones}{$zone}{long_description} = $element->findvalue('./description') ? $element->findvalue('./description') : "--"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:transmit_sizes' => $transmit_sizes, - 's2:return_code' => $return_code, + "firewalld::zones::${zone}::short_description" => $anvil->data->{firewalld}{zones}{$zone}{short_description}, + "firewalld::zones::${zone}::long_description" => $anvil->data->{firewalld}{zones}{$zone}{long_description}, }}); } - else - { - # Remote call - ($transmit_sizes, my $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - user => $remote_user, - password => $password, - remote_user => $remote_user, - }); + foreach my $service ($dom->findnodes('/zone/service')) + { + my $service_name = $service->{name}; + $anvil->data->{firewalld}{zones}{$zone}{service}{$service_name}{opened} = 1; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:transmit_sizes' => $transmit_sizes, - 's2:error' => $error, - 's3:return_code' => $return_code, + "firewalld::zones::${zone}::service::${service_name}::opened" => $anvil->data->{firewalld}{zones}{$zone}{service}{$service_name}{opened}, }}); } - foreach my $line (split/\n/, $transmit_sizes) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line =~ /rx:(\d+)/) - { - $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes} = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${in_iface}::rx_bytes" => $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes}." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{network}{$host}{interface}{$in_iface}{rx_bytes}}).")", - }}); - } - if ($line =~ /tx:(\d+)/) - { - $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes} = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${in_iface}::tx_bytes" => $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes}." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{network}{$host}{interface}{$in_iface}{tx_bytes}}).")", - }}); - } + foreach my $port ($dom->findnodes('/zone/port')) + { + my $port_number = $port->{port}; + my $port_protocol = $port->{protocol}; + $anvil->data->{firewalld}{zones}{$zone}{port}{$port_number}{protocol}{$port_protocol}{opened} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "firewalld::zones::${zone}::port::${port_number}::protocol::${port_protocol}::opened" => $anvil->data->{firewalld}{zones}{$zone}{port}{$port_number}{protocol}{$port_protocol}{opened}, + }}); } } } - next if not $in_iface; - if ($in_iface eq "lo") - { - # We don't care about 'lo'. - delete $anvil->data->{network}{$host}{interface}{$in_iface}; - next; - } - if ($line =~ /inet (.*?)\/(.*?) /) + closedir(DIRECTORY); + + # Check if any zones need to be added or managed. + foreach my $zone (sort {$a cmp $b} keys %{$anvil->data->{firewalld}{zones}}) { - my $ip = $1; - my $cidr = $2; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip => $ip, cidr => $cidr }}); - - my $subnet_mask = $cidr; - if (($cidr =~ /^\d{1,2}$/) && ($cidr >= 0) && ($cidr <= 32)) - { - # Convert to subnet mask - $subnet_mask = $anvil->Convert->cidr({cidr => $cidr}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { subnet_mask => $subnet_mask }}); - } - - $anvil->data->{network}{$host}{interface}{$in_iface}{ip} = $ip; - $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask} = $subnet_mask; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "s1:network::${host}::interface::${in_iface}::ip" => $anvil->data->{network}{$host}{interface}{$in_iface}{ip}, - "s2:network::${host}::interface::${in_iface}::subnet_mask" => $anvil->data->{network}{$host}{interface}{$in_iface}{subnet_mask}, + "firewalld::zones::${zone}::have" => $anvil->data->{firewalld}{zones}{$zone}{have}, + "firewalld::zones::${zone}::needed" => $anvil->data->{firewalld}{zones}{$zone}{needed}, }}); - } - if ($line =~ /ether (.*?) /i) - { - my $mac_address = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac_address => $mac_address }}); - # Wireless interfaces have a 'permaddr' that is stable. The MAC address shown by 'ether' changes constantly, for some odd reason. - if ($line =~ /permaddr (.*)$/) + # If this isn't a zone I need, ignore it completely. It might be something the user + # is doing. + next if not $anvil->data->{firewalld}{zones}{$zone}{needed}; + + # Is this zone one of ours? + if (($zone !~ /^IFN/) && ($zone !~ /^BCN/) && ($zone !~ /^SN/) && ($zone !~ /^MN/)) { - $mac_address = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mac_address => $mac_address }}); + # Not a zone we manage + next; } - $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address} = $mac_address; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${in_iface}::mac_address" => $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}, - }}); - - # If this is a bond or bridge, don't record the MAC address. It confuses things as - # they show the MAC of the active interface. If this is an interface, see if the file - # '/sys/class/net//bonding_slave/perm_hwaddr' exists and, if so, read the MAC - # address from there. If not, read the MAC address from - # '/sys/class/net//address'. - my $shell_call = 'IFACE='.$in_iface.' -if [ -e "/sys/class/net/${IFACE}/bridge" ]; -then - echo bridge; -elif [ -e "/proc/net/bonding/${IFACE}" ]; -then - echo bond; -else - ethtool -P ${IFACE} -fi'; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - if ($is_local) + # If the zone doesn't exist, create it. + if (not $anvil->data->{firewalld}{zones}{$zone}{have}) { - # Local call. - ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + # Create the zone. + $reload = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + + my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --new-zone=\"".$zone."\""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:return_code' => $return_code, + output => $output, + return_code => $return_code, }}); + + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => $debug, key => "log_0708", variables => { zone => $zone }}); } - else + + # Do any interfaces need to be added to this zone? + foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$host_name}{interface}}) { - # Remote call - ($output, my $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - user => $remote_user, - password => $password, - remote_user => $remote_user, - }); + next if not $anvil->data->{network}{$host_name}{interface}{$interface}{ip}; + my $interface_zone = uc(($interface =~ /^(.*?)_/)[0]); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:error' => $error, - 's3:return_code' => $return_code, + interface => $interface, + interface_zone => $interface_zone, }}); - } - foreach my $line (split/\n/, $output) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line =~ /^.*: (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)$/) + next if $interface_zone ne $zone; + + if (not exists $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}) { - my $real_mac = $1; - $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address} = $real_mac; + # Add it. + $reload = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=\"".$zone."\" --add-interface=\"".$interface."\""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${in_iface}::mac_address" => $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address}, + output => $output, + return_code => $return_code, }}); - # Make it easy to look up an interface name based on a given MAC - # address. - $anvil->data->{network}{$host}{mac_address}{$real_mac}{interface} = $in_iface; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::mac_address::${real_mac}::interface" => $anvil->data->{network}{$host}{mac_address}{$real_mac}{interface}, + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => $debug, key => "log_0709", variables => { + interface => $interface, + zone => $zone, }}); } } + + # Does the short description need to be updated? + if ($anvil->data->{firewalld}{zones}{$zone}{short_description} ne $zone) + { + # Update the short description + my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=\"".$zone."\" --set-short=\"".$zone."\""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + } + + # Does the long description need to be updated? + my $description = ""; + my $network = ""; + my $sequence = 0; + if ($zone =~ /^(.*?)(\d+)$/) + { + $network = $1; + $sequence = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + network => $network, + sequence => $sequence, + }}); + + if ($network eq "BCN") + { + $description = $anvil->Words->string({key => 'message_0160'})." ".$sequence; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { description => $description }}); + } + elsif ($network eq "SN") + { + $description = $anvil->Words->string({key => 'message_0161'})." ".$sequence; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { description => $description }}); + } + elsif ($network eq "IFN") + { + $description = $anvil->Words->string({key => 'message_0162'})." ".$sequence; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { description => $description }}); + } + elsif ($network eq "MN") + { + $description = $anvil->Words->string({key => 'message_0293'})." ".$sequence; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { description => $description }}); + } + } + + if (($description) && ($anvil->data->{firewalld}{zones}{$zone}{long_description} ne $description)) + { + my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=\"".$zone."\" --set-description=\"".$description."\""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + } + + # Now we need to decide what should be opened for each network. + if ($network) + { + # Load the ports we need to open for servers and DRBD resources. + $anvil->Network->_get_server_ports({debug => $debug}); + $anvil->Network->_get_drbd_ports({debug => $debug}); + + # Log found ports. + foreach my $port (sort {$a <=> $b} keys %{$anvil->data->{firewall}{server}{port}}) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "firewall::server::port::$port" => $anvil->data->{firewall}{server}{port}{$port} }}); + } + foreach my $port (sort {$a <=> $b} keys %{$anvil->data->{firewall}{drbd}{port}}) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "firewall::drbd::port::$port" => $anvil->data->{firewall}{drbd}{port}{$port} }}); + } + + # If we're a striker, make sure that postgresql + if ($host_type eq "striker") + { + my $changes = $anvil->Network->_manage_striker_firewall({debug => $debug, zone => $zone}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + if ($changes) + { + $reload = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + } + } + elsif ($host_type eq "node") + { + my $changes = $anvil->Network->_manage_node_firewall({debug => $debug, zone => $zone}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + if ($changes) + { + $reload = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + } + } + elsif ($host_type eq "dr") + { + my $changes = $anvil->Network->_manage_dr_firewall({debug => $debug, zone => $zone}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + if ($changes) + { + $reload = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + } + } + } } - if ($line =~ /mtu (\d+) /i) + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }}); + if ($reload) { - my $mtu = $1; - $anvil->data->{network}{$host}{interface}{$in_iface}{mtu} = $mtu; + # Reload + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0716"}); + + my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --reload"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${in_iface}::mtu" => $anvil->data->{network}{$host}{interface}{$in_iface}{mtu}, + output => $output, + return_code => $return_code, }}); } } - # Read the config files for the interfaces we've found. Use 'ls' to find the interface files. Then - # we'll read them all in. - $shell_call = $anvil->data->{path}{exe}{ls}." ".$anvil->data->{path}{directories}{ifcfg}; - $output = ""; - if ($is_local) + return(0); +} + + +=head2 ping + +This method will attempt to ping a target, by host name or IP, and returns C<< 1 >> if successful, and C<< 0 >> if not. + +Example; + + # Test access to the internet. Allow for three attempts to account for network jitter. + my ($pinged, $average_time) = $anvil->Network->ping({ + ping => "google.ca", + count => 3, + }); + + # Test 9000-byte jumbo-frame access to a target over the BCN. + my ($jumbo_to_peer, $average_time) = $anvil->Network->ping({ + ping => "an-a01n02.bcn", + count => 1, + payload => 9000, + fragment => 0, + }); + + # Check to see if an Anvil! node has internet access + my ($pinged, $average_time) = $anvil->Network->ping({ + target => "an-a01n01.alteeve.com", + port => 22, + password => "super secret", + remote_user => "admin", + ping => "google.ca", + count => 3, + }); + +Parameters; + +=head3 count (optional, default '1') + +This tells the method how many time to try to ping the target. The method will return as soon as any ping attemp succeeds (unlike pinging from the command line, which always pings the requested count times). + +=head3 debug (optional, default '3') + +This is an optional way to alter to level at which this method is logged. Useful when the caller is trying to debug a problem. Generally this can be ignored. + +=head3 fragment (optional, default '1') + +When set to C<< 0 >>, the ping will fail if the packet has to be fragmented. This is meant to be used along side C<< payload >> for testing MTU sizes. + +=head3 password (optional) + +This is the password used to access a remote machine. This is used when pinging from a remote machine to a given ping target. + +=head3 payload (optional) + +This can be used to force the ping packet size to a larger number of bytes. It is most often used along side C<< fragment => 0 >> as a way to test if jumbo frames are working as expected. + +B: The payload will have 28 bytes removed to account for ICMP overhead. So if you want to test an MTU of '9000', specify '9000' here. You do not need to account for the ICMP overhead yourself. + +=head3 port (optional, default '22') + +This is the port used to access a remote machine. This is used when pinging from a remote machine to a given ping target. + +B: See C<< Remote->call >> for additional information on specifying the SSH port as part of the target. + +=head3 remote_user (optional, default root) + +If C<< target >> is set, this is the user we will use to log into the remote machine to run the actual ping. + +=head3 target (optional) + +This is the host name or IP address of a remote machine that you want to run the ping on. This is used to test a remote machine's access to a given ping target. + +=head3 timeout (optional, default '1') + +This is how long we will wait for a ping to return, in seconds. Any real number is allowed (C<< 1 >> (one second), C<< 0.25 >> (1/4 second), etc). If set to C<< 0 >>, we will wait for the ping command to exit without limit. + +=cut +sub ping +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->ping()" }}); + +# my $start_time = [gettimeofday]; +# print "Start time: [".$start_time->[0].".".$start_time->[1]."]\n"; +# +# my $ping_time = tv_interval ($start_time, [gettimeofday]); +# print "[".$ping_time."] - Pinged: [$host]\n"; + + # If we were passed a target, try pinging from it instead of locally + my $count = defined $parameter->{count} ? $parameter->{count} : 1; # How many times to try to ping it? Will exit as soon as one succeeds + my $fragment = defined $parameter->{fragment} ? $parameter->{fragment} : 1; # Allow fragmented packets? Set to '0' to check MTU. + my $password = defined $parameter->{password} ? $parameter->{password} : ""; + my $payload = defined $parameter->{payload} ? $parameter->{payload} : 0; # The size of the ping payload. Use when checking MTU. + my $ping = defined $parameter->{ping} ? $parameter->{ping} : ""; + my $port = defined $parameter->{port} ? $parameter->{port} : ""; + my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; + my $target = defined $parameter->{target} ? $parameter->{target} : ""; + my $timeout = defined $parameter->{timeout} ? $parameter->{timeout} : 1; # This sets the 'timeout' delay. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + count => $count, + fragment => $fragment, + payload => $payload, + password => $anvil->Log->is_secure($password), + ping => $ping, + port => $port, + remote_user => $remote_user, + target => $target, + }}); + + # Was timeout specified as a simple integer? + if (($timeout !~ /^\d+$/) && ($timeout !~ /^\d+\.\d+$/)) + { + # The timeout was invalid, switch it to 1 + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { timeout => $timeout }}); + $timeout = 1; + } + + # If the payload was set, take 28 bytes off to account for ICMP overhead. + if ($payload) { - # Local call. - ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:return_code' => $return_code, - }}); + $payload -= 28; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { payload => $payload }}); } - else + + # Build the call. Note that we use 'timeout' because if there is no connection and the host name is + # used to ping and DNS is not available, it could take upwards of 30 seconds time timeout otherwise. + my $shell_call = ""; + if ($timeout) { - # Remote call - ($output, my $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - user => $remote_user, - password => $password, - remote_user => $remote_user, - }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:error' => $error, - 's3:return_code' => $return_code, - }}); + $shell_call = $anvil->data->{path}{exe}{timeout}." $timeout "; } - foreach my $line (split/\n/, $output) + $shell_call .= $anvil->data->{path}{exe}{'ping'}." -W 1 -n ".$ping." -c 1"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + if (not $fragment) { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - next if $line !~ /^ifcfg-/; + $shell_call .= " -M do"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + } + if ($payload) + { + $shell_call .= " -s $payload"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + } + $shell_call .= " || ".$anvil->data->{path}{exe}{echo}." timeout"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + + my $pinged = 0; + my $average_ping_time = 0; + foreach my $try (1..$count) + { + last if $pinged; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + count => $count, + try => $try, + }}); - my $full_path = $anvil->data->{path}{directories}{ifcfg}."/".$line; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { full_path => $full_path }}); + my $output = ""; + my $error = ""; - my $file_body = $anvil->Storage->read_file({ - debug => $debug, - file => $full_path, - target => $target, - password => $password, - port => $port, - remote_user => $remote_user, - }); + # If the 'target' is set, we'll call over SSH unless 'target' is our host name. + my $is_local = $anvil->Network->is_local({host => $target}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "s1:full_path" => $full_path, - "s2:file_body" => $file_body, + target => $target, + is_local => $is_local, }}); + if ($is_local) + { + ### Local calls + ($output, my $return_code) = $anvil->System->call({ + debug => $debug, + shell_call => $shell_call, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); + } + else + { + ### Remote calls + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0166", variables => { shell_call => $shell_call, target => $target, remote_user => $remote_user }}); + ($output, $error, my $return_code) = $anvil->Remote->call({ + debug => $debug, + shell_call => $shell_call, + target => $target, + port => $port, + password => $password, + remote_user => $remote_user, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + error => $error, + output => $output, + return_code => $return_code, + }}); + } - # Break it apart and store any variables. - my $temp = {}; - my $interface = ""; - foreach my $line (split/\n/, $file_body) + foreach my $line (split/\n/, $output) { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - next if $line =~ /^#/; - if ($line =~ /(.*?)=(.*)/) + if ($line =~ /(\d+) packets transmitted, (\d+) received/) { - my $variable = $1; - my $value = $2; - $value =~ s/^"(.*)"$/$1/; - $temp->{$variable} = $value; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "temp->{$variable}" => $temp->{$variable} }}); + # This isn't really needed, but might help folks watching the logs. + my $pings_sent = $1; + my $pings_received = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + pings_sent => $pings_sent, + pings_received => $pings_received, + }}); - if (uc($variable) eq "DEVICE") + if ($pings_received) { - # If this isn't a device we saw in 'ip addr', skip it by just not setting the interface variable - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { value => $value }}); - last if not exists $anvil->data->{network}{$host}{interface}{$value}; - - $interface = $value; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { interface => $interface }}); + # Contact! + $pinged = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { pinged => $pinged }}); + last; } - } - - if ($interface) - { - $anvil->data->{network}{$host}{interface}{$interface}{file} = $full_path; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${interface}::file" => $anvil->data->{network}{$host}{interface}{$interface}{file}, - }}); - foreach my $variable (sort {$a cmp $b} keys %{$temp}) + else { - $anvil->data->{network}{$host}{interface}{$interface}{variable}{$variable} = $temp->{$variable}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${interface}::file::variable::${variable}" => $anvil->data->{network}{$host}{interface}{$interface}{variable}{$variable}, - }}); + # Not yet... Sleep to give time for transient network problems to + # pass. + sleep 1; } } + if ($line =~ /min\/avg\/max\/mdev = .*?\/(.*?)\//) + { + $average_ping_time = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { average_ping_time => $average_ping_time }}); + } } } - # Get the routing info. - my $lowest_metric = 99999999; - my $route_interface = ""; - my $route_ip = ""; - $shell_call = $anvil->data->{path}{exe}{ip}." route show"; - $output = ""; + # 0 == Ping failed + # 1 == Ping success + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + pinged => $pinged, + average_ping_time => $average_ping_time, + }}); + return($pinged, $average_ping_time); +} + +=head2 read_nmcli + +This method reads and parses the C<< nmcli >> data. The data is stored as; + + nmcli::::uuid::::name + nmcli::::uuid::::type + nmcli::::uuid::::timestamp_unix + nmcli::::uuid::::timestamp + nmcli::::uuid::::autoconnect + nmcli::::uuid::::autoconnect_priority + nmcli::::uuid::::read_only + nmcli::::uuid::::dbus_path + nmcli::::uuid::::active + nmcli::::uuid::::device + nmcli::::uuid::::state + nmcli::::uuid::::active_path + nmcli::::uuid::::slave + nmcli::::uuid::::filename + +Where C<< uuid >> is the UUID of the connection. For C<< host >>, please see the parameter below. For information on what each value means, please see C<< man nmcli >>. + +For each of reference, the following to values are also stored; + + nmcli::::name_to_uuid:: + nmcli::::device_to_uuid:: + +Where C<< name >> is the value in the interface set by the C<< NAME= >> variable and C<< device >> is the interface name (as used in C<< ip >>) and as set in the C<< DEVICE= >> variable in the C<< ifcfg-X >> files. + +Parameters; + +=head3 host (optional, default 'target' or local short host name) + +This is the hash key under which the parsed C<< nmcli >> data is stored. By default, this is C<< local >> when called locally, or it will be C<< target >> if C<< target >> is passed. + +=head3 password (optional) + +This is the password used to access a remote machine. This is used when reading C<< nmcli >> data on a remote system. + +=head3 port (optional, default '22') + +This is the port used to access a remote machine. This is used when reading C<< nmcli >> data on a remote system. + +=head3 remote_user (optional, default root) + +If C<< target >> is set, this is the remote user we use to log into the remote system. + +=head3 target (optional) + +This is the host name or IP address of a remote machine that you want to read C<< nmcli >> data from. + +=cut +sub read_nmcli +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->read_nmcli()" }}); + + # If we were passed a target, try pinging from it instead of locally + my $password = defined $parameter->{password} ? $parameter->{password} : ""; + my $host = defined $parameter->{host} ? $parameter->{host} : ""; + my $port = defined $parameter->{port} ? $parameter->{port} : ""; + my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; + my $target = defined $parameter->{target} ? $parameter->{target} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + host => $host, + password => $anvil->Log->is_secure($password), + port => $port, + remote_user => $remote_user, + target => $target, + }}); + + if (not $host) + { + $host = $target ? $target : $anvil->Get->short_host_name(); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); + } + + if (exists $anvil->data->{nmcli}{$host}) + { + delete $anvil->data->{nmcli}{$host}; + } + + # Reading locally or remote? + my $shell_call = $anvil->data->{path}{exe}{nmcli}." --colors no --terse --fields name,device,state,type,uuid,filename connection show"; + my $output = ""; + my $is_local = $anvil->Network->is_local({host => $target}); if ($is_local) { # Local call. @@ -2472,804 +3654,953 @@ fi'; } foreach my $line (split/\n/, $output) { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line =~ /default via (.*?) dev (.*?) proto .*? metric (\d+)/i) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'line >>' => $line }}); + + $line =~ s/\\:/!col!/g; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'line <<' => $line }}); + + my ($name, $device, $state, $type, $uuid, $filename) = (split/:/, $line); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:name' => $name, + 's2:device' => $device, + 's3:state' => $state, + 's4:type' => $type, + 's5:uuid' => $uuid, + 's6:filename' => $filename, + }}); + if ($uuid) { - my $this_ip = $1; - my $this_interface = $2; - my $metric = $3; + # Inactive interfaces have a name but not a device; + if (not $device) + { + # Read the file, see if we can find it there. + if (-e $filename) + { + my $file_body = $anvil->Storage->read_file({debug => ($debug+1), file => $filename}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { file_body => $file_body }}); + + foreach my $line (split/\n/, $file_body) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + $line =~ s/#.*$//; + if ($line =~ /DEVICE=(.*)$/) + { + $device = $1; + $device =~ s/^\s+//; + $device =~ s/\s+$//; + $device =~ s/"(.*)"$/$1/; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { device => $device }}); + } + } + } + + if (not $device) + { + # Odd. Well, pull the device off the file name. + $device = ($filename =~ /\/ifcfg-(.*)$/)[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { device => $device }}); + } + } + + # Make it easy to look up a device's UUID by device or name. + $anvil->data->{nmcli}{$host}{name_to_uuid}{$name} = $uuid; + $anvil->data->{nmcli}{$host}{device_to_uuid}{$device} = $uuid; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:this_ip' => $this_ip, - 's2:this_interface' => $this_interface, - 's3:metric' => $metric, - 's4:lowest_metric' => $lowest_metric, + "nmcli::${host}::name_to_uuid::${name}" => $anvil->data->{nmcli}{$host}{name_to_uuid}{$name}, + "nmcli::${host}::device_to_uuid::${device}" => $anvil->data->{nmcli}{$host}{device_to_uuid}{$device}, }}); - if ($metric < $lowest_metric) + # Translate some values; + my $say_state = not $state ? 0 : 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_state => $say_state }}); + + # Now store the data + $anvil->data->{nmcli}{$host}{uuid}{$uuid}{name} = $name; + $anvil->data->{nmcli}{$host}{uuid}{$uuid}{device} = $device; + $anvil->data->{nmcli}{$host}{uuid}{$uuid}{'state'} = $state; + $anvil->data->{nmcli}{$host}{uuid}{$uuid}{type} = $type; + $anvil->data->{nmcli}{$host}{uuid}{$uuid}{filename} = $filename; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "nmcli::${host}::uuid::${uuid}::name" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{name}, + "nmcli::${host}::uuid::${uuid}::device" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{device}, + "nmcli::${host}::uuid::${uuid}::state" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{'state'}, + "nmcli::${host}::uuid::${uuid}::type" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{type}, + "nmcli::${host}::uuid::${uuid}::filename" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{filename}, + }}); + } + } + + return(0); +} + +############################################################################################################# +# Private functions # +############################################################################################################# + +# Return '1' if changed, '0' if not changed. +sub _check_firewalld_conf +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_check_firewalld_conf()" }}); + + # Read in the firewalld.conf file. + my $changes = 0; + my $new_firewalld_conf = ""; + my $old_firewalld_conf = $anvil->Storage->read_file({ + debug => $debug, + file => $anvil->data->{path}{configs}{'firewalld.conf'}, + force_read => 1, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { old_firewalld_conf => $old_firewalld_conf }}); + + # For now, the only thing we want to change is to disable 'AllowZoneDrifting' + # * firewalld[458395]: WARNING: AllowZoneDrifting is enabled. This is considered an insecure configuration option. It will be removed in a future release. Please consider disabling it now. + # Possible values; "yes", "no". Defaults to "yes". + my $allowzonedrifting_seen = 0; + foreach my $line (split/\n/, $old_firewalld_conf) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /^AllowZoneDrifting=(.*)$/) + { + my $old_value = $1; + $allowzonedrifting_seen = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + old_value => $old_value, + allowzonedrifting_seen => $allowzonedrifting_seen, + }}); + if ($old_value ne "no") { - $lowest_metric = $metric; - $route_interface = $this_interface; - $route_ip = $this_ip; + # Change needed. + $changes = 1; + $new_firewalld_conf .= "AllowZoneDrifting=no\n"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - lowest_metric => $lowest_metric, - route_interface => $route_interface, - route_ip => $route_ip, + changes => $changes, + new_firewalld_conf => $new_firewalld_conf, }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0707"}); + next; } } + $new_firewalld_conf .= $line."\n"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { new_firewalld_conf => $new_firewalld_conf }}); + } + if (not $allowzonedrifting_seen) + { + $new_firewalld_conf .= $anvil->Words->string({key => 'message_0292'})."\n"; + $new_firewalld_conf .= "AllowZoneDrifting=no\n"; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0707"}); + } + + if ($changes) + { + # Write the file out + $anvil->Storage->write_file({ + debug => $debug, + backup => 1, + overwrite => 1, + body => $new_firewalld_conf, + file => $anvil->data->{path}{configs}{'firewalld.conf'}, + user => "root", + group => "root", + mode => "0644", + }); } + + return($changes); +} + +sub _get_existing_zone_interfaces +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_get_existing_zone_interfaces()" }}); + + my $this_zone = ""; + my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --get-active-zones"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - route_interface => $route_interface, - route_ip => $route_ip, + output => $output, + return_code => $return_code, }}); - - # If I got a route, get the DNS. - if ($route_interface) + foreach my $line (split/\n/, $output) { - # I want to build the DNS list from only the interface that is used for routing. - my $in_interface = ""; - my $dns_list = ""; - my $dns_hash = {}; - my $shell_call = $anvil->data->{path}{exe}{nmcli}." dev show"; - my $output = ""; - if ($is_local) - { - # Local call. - ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:return_code' => $return_code, - }}); - } - else - { - # Remote call - ($output, my $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - user => $remote_user, - password => $password, - remote_user => $remote_user, - }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:error' => $error, - 's3:return_code' => $return_code, - }}); - } - foreach my $line (split/\n/, $output) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if ($line =~ /interfaces: (.*)$/) { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line =~ /GENERAL.DEVICE:\s+(.*)$/) - { - $in_interface = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { in_interface => $in_interface }}); - } - if (not $line) - { - $in_interface = ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { in_interface => $in_interface }}); - } - - next if $in_interface ne $route_interface; + my $interfaces = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { interfaces => $interfaces }}); + next if not $this_zone; - if ($line =~ /IP4.DNS\[(\d+)\]:\s+(.*)/i) + foreach my $interface (split/\s+/, $interfaces) { - my $order = $1; - my $ip = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { interface => $interface }}); - $dns_hash->{$order} = $ip; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "dns_hash->{$order}" => $dns_hash->{$order} }}); - } - } - - foreach my $order (sort {$a cmp $b} keys %{$dns_hash}) - { - $dns_list .= $dns_hash->{$order}.", "; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "s1:dns_hash->{$order}" => $dns_hash->{$order}, - "s2:dns_list" => $dns_list, - }}); + $anvil->data->{firewall}{zone}{$this_zone}{interface}{$interface} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "firewall::zone::${this_zone}::interface::${interface}" => $anvil->data->{firewall}{zone}{$this_zone}{interface}{$interface}, + }}); + } + } + else + { + $this_zone = $line; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_zone => $this_zone }}); } - $dns_list =~ s/, $//; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { dns_list => $dns_list }}); - - $anvil->data->{network}{$host}{interface}{$route_interface}{default_gateway} = 1; - $anvil->data->{network}{$host}{interface}{$route_interface}{gateway} = $route_ip; - $anvil->data->{network}{$host}{interface}{$route_interface}{dns} = $dns_list; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "network::${host}::interface::${route_interface}::default_gateway" => $anvil->data->{network}{$host}{interface}{$route_interface}{default_gateway}, - "network::${host}::interface::${route_interface}::gateway" => $anvil->data->{network}{$host}{interface}{$route_interface}{gateway}, - "network::${host}::interface::${route_interface}::dns" => $anvil->data->{network}{$host}{interface}{$route_interface}{dns}, - }}); } return(0); } -=head2 get_network - -This takes an IP address and subnet and returns the network it belongs too. For example; - - my $network = $anvil->Network->get_network({ip => "10.2.4.1", subnet_mask => "255.255.0.0"}); - -This would set C<< $network >> to C<< 10.2.0.0 >>. - -If the network can't be caluclated for any reason, and empty string will be returned. - -Parameters; - -=head3 ip (required) - -This is the IPv4 IP address being calculated. - -=head3 subnet_mask (required) - -This is the subnet mask of the IP address being calculated. - -=cut -sub get_network +# This looks for all servers running here and stores their ports in a hash. +sub _get_server_ports { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->get_network()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_get_server_ports()" }}); - my $network = ""; - my $ip = defined $parameter->{ip} ? $parameter->{ip} : ""; - my $subnet_mask = defined $parameter->{subnet_mask} ? $parameter->{subnet_mask} : ""; + my $shell_call = $anvil->data->{path}{exe}{virsh}." list --name"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - ip => $ip, - subnet_mask => $subnet_mask, + 's1:output' => $output, + 's2:return_code' => $return_code, }}); - if (not $ip) + my $servers = []; + foreach my $server (split/\n/, $output) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->get_network()", parameter => "ip" }}); - return(""); + $server = $anvil->Words->clean_spaces({string => $server}); + next if not $server; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server => $server }}); + + push @{$servers}, $server; } - if (not $subnet_mask) + + foreach my $server (sort {$a cmp $b} @{$servers}) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->get_network()", parameter => "subnet_mask" }}); - return(""); + my $shell_call = $anvil->data->{path}{exe}{virsh}." dumpxml ".$server; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, + }}); + + foreach my $line (split/\n/, $output) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + if (($line =~ /Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { port => $port }}); + + $anvil->data->{firewall}{server}{port}{$port} = $server; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "firewall::server::port::$port" => $anvil->data->{firewall}{server}{port}{$port}, + }}); + last; + } + } } - my $block = Net::Netmask->new($ip."/".$subnet_mask); - my $base = $block->base(); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { base => $base }}); + return(0); +} + +# This looks for all drbd resources configured here and stores their ports in a hash. +sub _get_drbd_ports +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_get_drbd_ports()" }}); - if ($anvil->Validate->ipv4({ip => $base})) + my $directory = $anvil->data->{path}{directories}{drbd_resources}; + if (not -d $directory) { - $network = $base; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { network => $network }}); + # DRBD isn't installed. + return(0); + } + + local(*DIRECTORY); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0018", variables => { directory => $directory }}); + opendir(DIRECTORY, $directory); + while(my $file = readdir(DIRECTORY)) + { + next if $file !~ /\.res$/; + my $full_path = $directory."/".$file; + $full_path =~ s/\/\//\//g; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { full_path => $full_path }}); + + my $file_body = $anvil->Storage->read_file({debug => ($debug+1), file => $full_path}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { file_body => $file_body }}); + + foreach my $line (split/\n/, $file_body) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + + if ($line =~ /host\s+.*?address.*?\s(\d.*?):(\d+);/) + { + my $ip = $1; + my $port = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:ip" => $ip, + "s2:port" => $port, + }}); + + $anvil->data->{firewall}{drbd}{port}{$port} = $ip; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "firewall::drbd::port::$port" => $anvil->data->{firewall}{drbd}{port}{$port}, + }}); + } + } } + closedir(DIRECTORY); - return($network); + return(0); } - -=head2 is_local - -This method takes a host name or IP address and looks to see if it matches the local system. If it does, it returns C<< 1 >>. Otherwise it returns C<< 0 >>. - -Parameters; - -=head3 host (required) - -This is the host name (or IP address) to check against the local system. - -=cut -### NOTE: Do not log in here, it will cause a recursive loop! -sub is_local +# This looks in qemu.conf for the minimum and maximum TCP ports +sub _get_live_migration_ports { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_get_live_migration_ports()" }}); - my $host = $parameter->{host} ? $parameter->{host} : ""; - return(1) if not $host; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - host => $host, - }}); + my $default_minimum = 49152; + my $default_maximum = 49215; + my $set_minimum = 0; + my $set_maximum = 0; - # If we've checked this host before, return the cached answer - if (exists $anvil->data->{cache}{is_local}{$host}) + my $file_body = $anvil->Storage->read_file({debug => $debug, file => $anvil->data->{path}{configs}{'qemu.conf'}}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { file_body => $file_body }}); + foreach my $line (split/\n/, $file_body) { - return($anvil->data->{cache}{is_local}{$host}); + if (($line =~ /^#/) && ($line =~ /migration_port_min.*?=.*?(\d+)$/)) + { + $default_minimum = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { default_minimum => $default_minimum }}); + } + elsif (($line =~ /^#/) && ($line =~ /migration_port_max.*?=.*?(\d+)$/)) + { + $default_maximum = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { default_maximum => $default_maximum }}); + } + elsif ($line =~ /migration_port_min.*?=.*?(\d+)$/) + { + $set_minimum = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { set_minimum => $set_minimum }}); + } + elsif ($line =~ /migration_port_max.*?=.*?(\d+)$/) + { + $set_maximum = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { set_maximum => $set_maximum }}); + } } - $anvil->data->{cache}{is_local}{$host} = 0; - if (($host eq $anvil->Get->host_name) or - ($host eq $anvil->Get->short_host_name) or - ($host eq "localhost") or - ($host eq "127.0.0.1")) + if (not $set_minimum) { - # It's local - $anvil->data->{cache}{is_local}{$host} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "cache::is_local::${host}" => $anvil->data->{cache}{is_local}{$host} }}); + $set_minimum = $default_minimum; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { set_minimum => $set_minimum }}); } - else + if (not $set_maximum) { - # Get the list of current IPs and see if they match. - my $local_host = $anvil->Get->short_host_name(); - if (not exists $anvil->data->{network}{$local_host}{interface}) - { - $anvil->Network->get_ips({debug => 9999}); - } - foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$local_host}{interface}}) - { - next if not defined $anvil->data->{network}{$local_host}{interface}{$interface}{ip}; - if ($host eq $anvil->data->{network}{$local_host}{interface}{$interface}{ip}) - { - $anvil->data->{cache}{is_local}{$host} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "cache::is_local::${host}" => $anvil->data->{cache}{is_local}{$host} }}); - last; - } - } + $set_maximum = $default_maximum; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { set_maximum => $set_maximum }}); } - #$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { is_local => $is_local }}); - return($anvil->data->{cache}{is_local}{$host}); + return($set_minimum, $set_maximum); } - -=head2 is_our_interface - -This method takes an interface name and returns C<< 1 >> if the interface is one of the ones we manage (A C<< BCN >>, C<< IFN >>, C<< SN >> or C<< MN >> interface). If not, C<< 0 >> is returned. - -Parameters; - -=head3 interface (required) - -This is the name of the interface being evaluated. - -=cut -sub is_our_interface +sub _manage_port { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->is_our_interface()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_manage_port()" }}); - my $interface = $parameter->{interface} ? $parameter->{interface} : ""; + my $port = defined $parameter->{port} ? $parameter->{port} : ""; + my $protocol = defined $parameter->{protocol} ? $parameter->{protocol} : ""; + my $task = defined $parameter->{task} ? $parameter->{task} : ""; + my $zone = defined $parameter->{zone} ? $parameter->{zone} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - interface => $interface, + port => $port, + protocol => $protocol, + task => $task, + zone => $zone, }}); - if (not $interface) + if ((not $port) or (not $protocol) or (not $task) or (not $zone)) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_our_interface()", parameter => "interface" }}); + return("!!error!!"); + } + if (($task ne "close") && ($task ne "open")) + { + return("!!error!!"); + } + if (($protocol ne "tcp") && ($protocol ne "udp")) + { + return("!!error!!"); + } + if ($port =~ /^(\d+)-(\d+)$/) + { + my $minimum_port = $1; + my $maximum_port = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:minimum_port" => $minimum_port, + "s2:maximum_port" => $maximum_port, + }}); + + if (($minimum_port < 1) or ($minimum_port > 66635) or + ($maximum_port < 1) or ($maximum_port > 66635)) + { + return("!!error!!"); + } + } + elsif (($port =~ /\D/) or ($port < 1) or ($port > 65535)) + { + return("!!error!!"); + } + + # Do we need to actually do this? + if ($task eq "close") + { + # Is it open? + if ((not exists $anvil->data->{firewalld}{zones}{$zone}{port}{$port}{protocol}{$protocol}) or + (not $anvil->data->{firewalld}{zones}{$zone}{port}{$port}{protocol}{$protocol}{opened})) + { + # Already closed. + return(0); + } + } + elsif ($anvil->data->{firewalld}{zones}{$zone}{port}{$port}{protocol}{$protocol}{opened}) + { + # Already opened. return(0); } - my $ours = 0; - if (($interface =~ /^bcn/i) or - ($interface =~ /^sn/i) or - ($interface =~ /^ifn/i) or - ($interface =~ /^mn/i)) + my $shell_call = ""; + if ($task eq "close") { - $ours = 1; + $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=".$zone." --remove-port=".$port."/".$protocol; + if ($port =~ /-/) + { + # Range + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0715", variables => { + port => $port, + protocol => $protocol, + zone => $zone, + }}); + } + else + { + # Single port + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0714", variables => { + port => $port, + protocol => $protocol, + zone => $zone, + }}); + } } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ours => $ours }}); - return($ours); -} - -=head2 is_ip_in_network - -This takes an IP address, along with network and subnet mask and sees if the IP address is within the network. If it is, it returns C<< 1 >>. If the IP address doesn't match the network, C<< 0 >> is returned. - -Parameters - -=head3 ip (required) - -This is the ip IP address being analyzed. - -=head3 network (required) - -This is the IP address that will be paired with the subnet mask to see if the ip matches. - -=head3 subnet_mask (required) - -This is the subnet mask paired against the IP address used to check the ip against. + else + { + $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=".$zone." --add-port=".$port."/".$protocol; + if ($port =~ /-/) + { + # Range + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0713", variables => { + port => $port, + protocol => $protocol, + zone => $zone, + }}); + } + else + { + # Single port + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0712", variables => { + port => $port, + protocol => $protocol, + zone => $zone, + }}); + } + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 'output' => $output, + 'return_code' => $return_code, + }}); + + return(1); +} -=cut -sub is_ip_in_network +# Returns '0' if nothing was done, '1' otherwise. +sub _manage_service { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->is_ip_in_network()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_manage_service()" }}); - my $ip = defined $parameter->{ip} ? $parameter->{ip} : ""; - my $network = defined $parameter->{network} ? $parameter->{network} : ""; - my $subnet_mask = defined $parameter->{subnet_mask} ? $parameter->{subnet_mask} : ""; + my $service = defined $parameter->{service} ? $parameter->{service} : ""; + my $task = defined $parameter->{task} ? $parameter->{task} : ""; + my $zone = defined $parameter->{zone} ? $parameter->{zone} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - ip => $ip, - network => $network, - subnet_mask => $subnet_mask, + service => $service, + task => $task, + zone => $zone, }}); - if (not $network) + if ((not $service) or (not $task) or (not $zone)) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_ip_in_network()", parameter => "network" }}); - return(0); + return("!!error!!"); } - elsif (not $anvil->Validate->ipv4({ip => $network})) + if (($task ne "close") && ($task ne "open")) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "warning_0019", variables => { parameter => "network", network => $network }}); - return(0); + return("!!error!!"); } - if (not $ip) + + # Do we actually need to do something? + if ($task eq "close") { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_ip_in_network()", parameter => "ip" }}); - return(0); + # Is it open? + if ((not exists $anvil->data->{firewalld}{zones}{$zone}{service}{$service}) or + (not $anvil->data->{firewalld}{zones}{$zone}{service}{$service}{opened})) + { + # Already closed. + return(0); + } } - elsif (not $anvil->Validate->ipv4({ip => $ip})) + elsif ($anvil->data->{firewalld}{zones}{$zone}{service}{$service}{opened}) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "warning_0019", variables => { parameter => "ip", network => $ip }}); + # Already opened. return(0); } - if (not $subnet_mask) + + + my $shell_call = ""; + if ($task eq "close") { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Network->is_ip_in_network()", parameter => "subnet_mask" }}); - return(0); + $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=".$zone." --remove-service=".$service; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0711", variables => { + service => $service, + zone => $zone, + }}); } - elsif (not $anvil->Validate->subnet_mask({subnet_mask => $subnet_mask})) + else { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "warning_0020", variables => { parameter => "subnet_mask", subnet_mask => $subnet_mask }}); - return(0); + $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --zone=".$zone." --add-service=".$service; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0710", variables => { + service => $service, + zone => $zone, + }}); } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - my $match = 0; - my $block = Net::Netmask->new($network."/".$subnet_mask); - if ($block->match($ip)) - { - # This is a match! - $match = 1; - } + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 'output' => $output, + 'return_code' => $return_code, + }}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { match => $match }}); - return($match); + return(1); } -=head2 ping - -This method will attempt to ping a target, by host name or IP, and returns C<< 1 >> if successful, and C<< 0 >> if not. - -Example; - - # Test access to the internet. Allow for three attempts to account for network jitter. - my ($pinged, $average_time) = $anvil->Network->ping({ - ping => "google.ca", - count => 3, - }); - - # Test 9000-byte jumbo-frame access to a target over the BCN. - my ($jumbo_to_peer, $average_time) = $anvil->Network->ping({ - ping => "an-a01n02.bcn", - count => 1, - payload => 9000, - fragment => 0, - }); - - # Check to see if an Anvil! node has internet access - my ($pinged, $average_time) = $anvil->Network->ping({ - target => "an-a01n01.alteeve.com", - port => 22, - password => "super secret", - remote_user => "admin", - ping => "google.ca", - count => 3, - }); - -Parameters; - -=head3 count (optional, default '1') - -This tells the method how many time to try to ping the target. The method will return as soon as any ping attemp succeeds (unlike pinging from the command line, which always pings the requested count times). - -=head3 debug (optional, default '3') - -This is an optional way to alter to level at which this method is logged. Useful when the caller is trying to debug a problem. Generally this can be ignored. - -=head3 fragment (optional, default '1') - -When set to C<< 0 >>, the ping will fail if the packet has to be fragmented. This is meant to be used along side C<< payload >> for testing MTU sizes. - -=head3 password (optional) - -This is the password used to access a remote machine. This is used when pinging from a remote machine to a given ping target. - -=head3 payload (optional) - -This can be used to force the ping packet size to a larger number of bytes. It is most often used along side C<< fragment => 0 >> as a way to test if jumbo frames are working as expected. - -B: The payload will have 28 bytes removed to account for ICMP overhead. So if you want to test an MTU of '9000', specify '9000' here. You do not need to account for the ICMP overhead yourself. - -=head3 port (optional, default '22') - -This is the port used to access a remote machine. This is used when pinging from a remote machine to a given ping target. - -B: See C<< Remote->call >> for additional information on specifying the SSH port as part of the target. - -=head3 remote_user (optional, default root) - -If C<< target >> is set, this is the user we will use to log into the remote machine to run the actual ping. - -=head3 target (optional) - -This is the host name or IP address of a remote machine that you want to run the ping on. This is used to test a remote machine's access to a given ping target. - -=head3 timeout (optional, default '1') - -This is how long we will wait for a ping to return, in seconds. Any real number is allowed (C<< 1 >> (one second), C<< 0.25 >> (1/4 second), etc). If set to C<< 0 >>, we will wait for the ping command to exit without limit. - -=cut -sub ping +sub _manage_dr_firewall { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->ping()" }}); - -# my $start_time = [gettimeofday]; -# print "Start time: [".$start_time->[0].".".$start_time->[1]."]\n"; -# -# my $ping_time = tv_interval ($start_time, [gettimeofday]); -# print "[".$ping_time."] - Pinged: [$host]\n"; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_manage_dr_firewall()" }}); - # If we were passed a target, try pinging from it instead of locally - my $count = defined $parameter->{count} ? $parameter->{count} : 1; # How many times to try to ping it? Will exit as soon as one succeeds - my $fragment = defined $parameter->{fragment} ? $parameter->{fragment} : 1; # Allow fragmented packets? Set to '0' to check MTU. - my $password = defined $parameter->{password} ? $parameter->{password} : ""; - my $payload = defined $parameter->{payload} ? $parameter->{payload} : 0; # The size of the ping payload. Use when checking MTU. - my $ping = defined $parameter->{ping} ? $parameter->{ping} : ""; - my $port = defined $parameter->{port} ? $parameter->{port} : ""; - my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; - my $target = defined $parameter->{target} ? $parameter->{target} : ""; - my $timeout = defined $parameter->{timeout} ? $parameter->{timeout} : 1; # This sets the 'timeout' delay. + my $zone = defined $parameter->{zone} ? $parameter->{zone} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - count => $count, - fragment => $fragment, - payload => $payload, - password => $anvil->Log->is_secure($password), - ping => $ping, - port => $port, - remote_user => $remote_user, - target => $target, + zone => $zone, }}); - # Was timeout specified as a simple integer? - if (($timeout !~ /^\d+$/) && ($timeout !~ /^\d+\.\d+$/)) + # Set the services we want opened. + my $changes = 0; + my @bcn_services = ("audit", "ssh", "zabbix-agent", "zabbix-server"); + my @ifn_services = ("audit", "ssh", "zabbix-agent", "zabbix-server"); + my @sn_services = ("ssh"); # May use as a backup corosync network later + + # We need to make sure that the postgresql service is open for all networks. + if ($zone =~ /BCN/) { - # The timeout was invalid, switch it to 1 - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { timeout => $timeout }}); - $timeout = 1; + foreach my $service (@bcn_services) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } } - - # If the payload was set, take 28 bytes off to account for ICMP overhead. - if ($payload) + if ($zone =~ /IFN/) { - $payload -= 28; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { payload => $payload }}); + foreach my $service (@ifn_services) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } } - # Build the call. Note that we use 'timeout' because if there is no connection and the host name is - # used to ping and DNS is not available, it could take upwards of 30 seconds time timeout otherwise. - my $shell_call = ""; - if ($timeout) + # Open VNC/Spice/etc ports for servers/ + if (($zone =~ /BCN/) or ($zone =~ /IFN/)) { - $shell_call = $anvil->data->{path}{exe}{timeout}." $timeout "; + foreach my $port (sort {$a cmp $b} keys %{$anvil->data->{firewall}{server}{port}}) + { + # Make sure the port is open. + my $chenged = $anvil->Network->_manage_port({ + debug => $debug, + port => $port, + protocol => "tcp", + task => "open", + zone => $zone, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } + + # Open up live migration ports. It's possible that DR could migrate off to a prod Anvil! + # post-incident. + my ($migration_minimum, $migration_maximum) = $anvil->Network->_get_live_migration_ports({debug => $debug}); + my $range = $migration_minimum."-".$migration_maximum; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:migration_minimum" => $migration_minimum, + "s2:migration_maximum" => $migration_maximum, + "s3:range" => $range, + "s4:zone" => $zone, + }}); + + my $chenged = $anvil->Network->_manage_port({ + debug => $debug, + port => $range, + protocol => "tcp", + task => "open", + zone => $zone, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } } - $shell_call .= $anvil->data->{path}{exe}{'ping'}." -W 1 -n ".$ping." -c 1"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - if (not $fragment) + + if ($zone =~ /SN/) { - $shell_call .= " -M do"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + foreach my $service (@sn_services) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } + + # Open all the ports DRBD needs. + foreach my $port (sort {$a <=> $b} keys %{$anvil->data->{firewall}{drbd}{port}}) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + port => $port, + zone => $zone, + }}); + my $chenged = $anvil->Network->_manage_port({ + debug => $debug, + port => $port, + protocol => "tcp", + task => "open", + zone => $zone, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } } - if ($payload) + + return($changes); +} + +sub _manage_node_firewall +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_manage_node_firewall()" }}); + + my $zone = defined $parameter->{zone} ? $parameter->{zone} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + zone => $zone, + }}); + + # We open dhcp, tftp, and dns on the BCN for the install target feature. DNS is not currently + # provided, but it should be added later. + my $changes = 0; + my @bcn_services = ("audit", "high-availability", "ssh", "zabbix-agent", "zabbix-server"); + my @ifn_services = ("audit", "ssh", "zabbix-agent", "zabbix-server"); + my @sn_services = ("high-availability", "ssh"); # May use as a backup corosync network later + my @mn_services = ("high-availability", "ssh"); # May use as a backup corosync network later + + # We need to make sure that the postgresql service is open for all networks. + if ($zone =~ /BCN/) { - $shell_call .= " -s $payload"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); + foreach my $service (@bcn_services) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } } - $shell_call .= " || ".$anvil->data->{path}{exe}{echo}." timeout"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my $pinged = 0; - my $average_ping_time = 0; - foreach my $try (1..$count) + if ($zone =~ /IFN/) { - last if $pinged; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - count => $count, - try => $try, - }}); - - my $output = ""; - my $error = ""; - - # If the 'target' is set, we'll call over SSH unless 'target' is our host name. - my $is_local = $anvil->Network->is_local({host => $target}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - target => $target, - is_local => $is_local, - }}); - if ($is_local) + foreach my $service (@ifn_services) { - ### Local calls - ($output, my $return_code) = $anvil->System->call({ - debug => $debug, - shell_call => $shell_call, + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + } + } + + # Open VNC/Spice/etc ports for servers/ + if (($zone =~ /BCN/) or ($zone =~ /IFN/)) + { + foreach my $port (sort {$a cmp $b} keys %{$anvil->data->{firewall}{server}{port}}) + { + # Make sure the port is open. + my $chenged = $anvil->Network->_manage_port({ + debug => $debug, + port => $port, + protocol => "tcp", + task => "open", + zone => $zone, }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } } - else + } + + if ($zone =~ /SN/) + { + foreach my $service (@sn_services) { - ### Remote calls - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0166", variables => { shell_call => $shell_call, target => $target, remote_user => $remote_user }}); - ($output, $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - port => $port, - password => $password, - remote_user => $remote_user, + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - error => $error, - output => $output, - return_code => $return_code, - }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } } - foreach my $line (split/\n/, $output) + # Open all the ports DRBD needs. + foreach my $port (sort {$a <=> $b} keys %{$anvil->data->{firewall}{drbd}{port}}) { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line =~ /(\d+) packets transmitted, (\d+) received/) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + port => $port, + zone => $zone, + }}); + my $chenged = $anvil->Network->_manage_port({ + debug => $debug, + port => $port, + protocol => "tcp", + task => "open", + zone => $zone, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) { - # This isn't really needed, but might help folks watching the logs. - my $pings_sent = $1; - my $pings_received = $2; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - pings_sent => $pings_sent, - pings_received => $pings_received, - }}); - - if ($pings_received) - { - # Contact! - $pinged = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { pinged => $pinged }}); - last; - } - else - { - # Not yet... Sleep to give time for transient network problems to - # pass. - sleep 1; - } + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); } - if ($line =~ /min\/avg\/max\/mdev = .*?\/(.*?)\//) + } + } + + if ($zone =~ /MN/) + { + foreach my $service (@mn_services) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) { - $average_ping_time = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { average_ping_time => $average_ping_time }}); + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); } } } - # 0 == Ping failed - # 1 == Ping success - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - pinged => $pinged, - average_ping_time => $average_ping_time, - }}); - return($pinged, $average_ping_time); + if (($zone =~ /BCN/) or ($zone =~ /MN/)) + { + ### TODO: Find any old instances with a different port range and remove it if needed. + # Open up live migration ports + my ($migration_minimum, $migration_maximum) = $anvil->Network->_get_live_migration_ports({debug => $debug}); + my $range = $migration_minimum."-".$migration_maximum; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:migration_minimum" => $migration_minimum, + "s2:migration_maximum" => $migration_maximum, + "s3:range" => $range, + "s4:zone" => $zone, + }}); + + my $chenged = $anvil->Network->_manage_port({ + debug => $debug, + port => $range, + protocol => "tcp", + task => "open", + zone => $zone, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) + { + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); + } + + } + + return($changes); } -=head2 read_nmcli - -This method reads and parses the C<< nmcli >> data. The data is stored as; - - nmcli::::uuid::::name - nmcli::::uuid::::type - nmcli::::uuid::::timestamp_unix - nmcli::::uuid::::timestamp - nmcli::::uuid::::autoconnect - nmcli::::uuid::::autoconnect_priority - nmcli::::uuid::::read_only - nmcli::::uuid::::dbus_path - nmcli::::uuid::::active - nmcli::::uuid::::device - nmcli::::uuid::::state - nmcli::::uuid::::active_path - nmcli::::uuid::::slave - nmcli::::uuid::::filename - -Where C<< uuid >> is the UUID of the connection. For C<< host >>, please see the parameter below. For information on what each value means, please see C<< man nmcli >>. - -For each of reference, the following to values are also stored; - - nmcli::::name_to_uuid:: - nmcli::::device_to_uuid:: - -Where C<< name >> is the value in the interface set by the C<< NAME= >> variable and C<< device >> is the interface name (as used in C<< ip >>) and as set in the C<< DEVICE= >> variable in the C<< ifcfg-X >> files. - -Parameters; - -=head3 host (optional, default 'target' or local short host name) - -This is the hash key under which the parsed C<< nmcli >> data is stored. By default, this is C<< local >> when called locally, or it will be C<< target >> if C<< target >> is passed. - -=head3 password (optional) - -This is the password used to access a remote machine. This is used when reading C<< nmcli >> data on a remote system. - -=head3 port (optional, default '22') - -This is the port used to access a remote machine. This is used when reading C<< nmcli >> data on a remote system. - -=head3 remote_user (optional, default root) - -If C<< target >> is set, this is the remote user we use to log into the remote system. - -=head3 target (optional) - -This is the host name or IP address of a remote machine that you want to read C<< nmcli >> data from. - -=cut -sub read_nmcli +sub _manage_striker_firewall { my $self = shift; my $parameter = shift; my $anvil = $self->parent; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->read_nmcli()" }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->_manage_striker_firewall()" }}); - # If we were passed a target, try pinging from it instead of locally - my $password = defined $parameter->{password} ? $parameter->{password} : ""; - my $host = defined $parameter->{host} ? $parameter->{host} : ""; - my $port = defined $parameter->{port} ? $parameter->{port} : ""; - my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; - my $target = defined $parameter->{target} ? $parameter->{target} : ""; + my $zone = defined $parameter->{zone} ? $parameter->{zone} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - host => $host, - password => $anvil->Log->is_secure($password), - port => $port, - remote_user => $remote_user, - target => $target, + zone => $zone, }}); - if (not $host) + if (($zone !~ /^BCN/) && ($zone !~ /^IFN/)) { - $host = $target ? $target : $anvil->Get->short_host_name(); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); + # Not a zone used by striker. + return(0); } - if (exists $anvil->data->{nmcli}{$host}) - { - delete $anvil->data->{nmcli}{$host}; - } + # We open dhcp, tftp, and dns on the BCN for the install target feature. DNS is not currently + # provided, but it should be added later. + my $changes = 0; + my @services = ("audit", "http", "https", "postgresql", "ssh", "vnc-server", "zabbix-agent", "zabbix-server"); + my @bcn_services = ("dhcp", "dns", "tftp"); + my @ifn_services = (); - # Reading locally or remote? - my $shell_call = $anvil->data->{path}{exe}{nmcli}." --colors no --terse --fields name,device,state,type,uuid,filename connection show"; - my $output = ""; - my $is_local = $anvil->Network->is_local({host => $target}); - if ($is_local) + # We need to make sure that the postgresql service is open for all networks. + if ($zone =~ /BCN/) { - # Local call. - ($output, my $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:return_code' => $return_code, - }}); + foreach my $service (@bcn_services) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + push @services, $service; + } } - else + foreach my $service (@services) { - # Remote call - ($output, my $error, my $return_code) = $anvil->Remote->call({ - debug => $debug, - shell_call => $shell_call, - target => $target, - user => $remote_user, - password => $password, - remote_user => $remote_user, + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); + my $chenged = $anvil->Network->_manage_service({ + debug => $debug, + service => $service, + zone => $zone, + task => "open", }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:output' => $output, - 's2:error' => $error, - 's3:return_code' => $return_code, - }}); - } - foreach my $line (split/\n/, $output) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'line >>' => $line }}); - - $line =~ s/\\:/!col!/g; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'line <<' => $line }}); - - my ($name, $device, $state, $type, $uuid, $filename) = (split/:/, $line); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 's1:name' => $name, - 's2:device' => $device, - 's3:state' => $state, - 's4:type' => $type, - 's5:uuid' => $uuid, - 's6:filename' => $filename, - }}); - if ($uuid) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { chenged => $chenged }}); + if ($chenged) { - # Inactive interfaces have a name but not a device; - if (not $device) - { - # Read the file, see if we can find it there. - if (-e $filename) - { - my $file_body = $anvil->Storage->read_file({debug => ($debug+1), file => $filename}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { file_body => $file_body }}); - - foreach my $line (split/\n/, $file_body) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - $line =~ s/#.*$//; - if ($line =~ /DEVICE=(.*)$/) - { - $device = $1; - $device =~ s/^\s+//; - $device =~ s/\s+$//; - $device =~ s/"(.*)"$/$1/; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { device => $device }}); - } - } - } - - if (not $device) - { - # Odd. Well, pull the device off the file name. - $device = ($filename =~ /\/ifcfg-(.*)$/)[0]; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { device => $device }}); - } - } - - # Make it easy to look up a device's UUID by device or name. - $anvil->data->{nmcli}{$host}{name_to_uuid}{$name} = $uuid; - $anvil->data->{nmcli}{$host}{device_to_uuid}{$device} = $uuid; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "nmcli::${host}::name_to_uuid::${name}" => $anvil->data->{nmcli}{$host}{name_to_uuid}{$name}, - "nmcli::${host}::device_to_uuid::${device}" => $anvil->data->{nmcli}{$host}{device_to_uuid}{$device}, - }}); - - # Translate some values; - my $say_state = not $state ? 0 : 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_state => $say_state }}); - - # Now store the data - $anvil->data->{nmcli}{$host}{uuid}{$uuid}{name} = $name; - $anvil->data->{nmcli}{$host}{uuid}{$uuid}{device} = $device; - $anvil->data->{nmcli}{$host}{uuid}{$uuid}{'state'} = $state; - $anvil->data->{nmcli}{$host}{uuid}{$uuid}{type} = $type; - $anvil->data->{nmcli}{$host}{uuid}{$uuid}{filename} = $filename; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "nmcli::${host}::uuid::${uuid}::name" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{name}, - "nmcli::${host}::uuid::${uuid}::device" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{device}, - "nmcli::${host}::uuid::${uuid}::state" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{'state'}, - "nmcli::${host}::uuid::${uuid}::type" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{type}, - "nmcli::${host}::uuid::${uuid}::filename" => $anvil->data->{nmcli}{$host}{uuid}{$uuid}{filename}, - }}); + $changes = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { changes => $changes }}); } } - return(0); + return($changes); } -############################################################################################################# -# Private functions # -############################################################################################################# - 1; diff --git a/Anvil/Tools/Server.pm b/Anvil/Tools/Server.pm index c5ed6079..b5e9c300 100644 --- a/Anvil/Tools/Server.pm +++ b/Anvil/Tools/Server.pm @@ -200,6 +200,9 @@ sub boot_virsh host => $anvil->data->{server}{location}{$server}{host_name}, }}); + # Make sure the VNC port is open. + $anvil->Network->manage_firewall({debug => $debug}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::database::connections" => $anvil->data->{sys}{database}{connections} }}); if ($anvil->data->{sys}{database}{connections}) { @@ -1102,6 +1105,34 @@ WHERE } $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { migration_command => $migration_command }}); + # Register a job for the peer so it can update its firewall once the target is created. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "sys::database::connections" => $anvil->data->{sys}{database}{connections} + }}); + if ($anvil->data->{sys}{database}{connections}) + { + my $target_host_uuid = $anvil->Get->host_uuid_from_name({ + debug => $debug, + host_name => $target, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target_host_uuid => $target_host_uuid }}); + if ($target_host_uuid) + { + my ($job_uuid) = $anvil->Database->insert_or_update_jobs({ + file => $THIS_FILE, + line => __LINE__, + job_command => $anvil->data->{path}{exe}{'anvil-manage-firewall'}.$anvil->Log->switches, + job_data => "server=".$server, + job_name => "manage::firewall", + job_title => "job_0399", + job_description => "job_0400", + job_progress => 0, + job_host_uuid => $target_host_uuid, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { job_uuid => $job_uuid }}); + } + } + # Call the migration now my ($output, $return_code) = $anvil->System->call({shell_call => $migration_command}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm index 7afbc752..0626d229 100644 --- a/Anvil/Tools/Storage.pm +++ b/Anvil/Tools/Storage.pm @@ -3607,7 +3607,7 @@ This is the name of the file to read. When reading from a remote machine, it mus =head3 force_read (optional, default '1') -This is an otpional parameter that, if set to C<< 0 >>, will allow an existing cached copy of the file to be used instead of actually reading the file from disk (again). +This is an optional parameter that, if set to C<< 0 >>, will allow an existing cached copy of the file to be used instead of actually reading the file from disk (again). =head3 password (optional) diff --git a/Anvil/Tools/System.pm b/Anvil/Tools/System.pm index 08f02524..3afc4c8b 100644 --- a/Anvil/Tools/System.pm +++ b/Anvil/Tools/System.pm @@ -3475,6 +3475,7 @@ sub check_firewall # Show live or permanent rules? Permanent is default my $permanent = defined $parameter->{permanent} ? $parameter->{permanent} : 1; + my $start = defined $parameter->{start} ? $parameter->{start} : 1; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { permanent => $permanent }}); # Read in /etc/firewalld/firewalld.conf and parse the 'DefaultZone' variable. @@ -3654,319 +3655,6 @@ sub manage_authorized_keys return(0); } -=head2 manage_firewall - -This method manages a firewalld firewall. - -B: This is pretty basic at this time. Capabilities will be added over time so please expect changes to this method. - -Parameters; - -=head3 task (optional) - -If set to C<< open >>, it will open the corresponding C<< port >>. If set to C<< close >>, it will close the corresponding C<< port >>. If set to c<< check >>, the state of the given C<< port >> is returned. - -The default is C<< check >>. - -=head3 port_number (required) - -This is the port number to work on. - -If not specified, C<< service >> is required. - -=head3 protocol (optional) - -This can be c<< tcp >> or C<< upd >> and is used to specify what protocol to use with the C<< port >>, when specified. The default is C<< tcp >>. - -=cut -### TODO: This is slooooow. We need to be able to get more data per system call. -### - Getting better... -sub manage_firewall -{ - my $self = shift; - my $parameter = shift; - my $anvil = $self->parent; - my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->manage_firewall()" }}); - - my $task = defined $parameter->{task} ? $parameter->{task} : "check"; - my $port_number = defined $parameter->{port_number} ? $parameter->{port_number} : ""; - my $protocol = defined $parameter->{protocol} ? $parameter->{protocol} : "tcp"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - task => $task, - port_number => $port_number, - protocol => $protocol, - }}); - - ### NOTE: Disabled during development - return(0); - - # Make sure we have a port or service. - if (not $port_number) - { - # ... - return("!!error!!"); - } - if (($protocol ne "tcp") && ($protocol ne "udp")) - { - # Bad protocol - return("!!error!!"); - } - - # This will be set if the port is found to be open. - my $open = 0; - - # Checking the iptables rules in memory is very fast, relative to firewall-cmd. So we'll do an - # initial check there to see if the port in question is listed. - my $shell_call = $anvil->data->{path}{exe}{'iptables-save'}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my ($iptables, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - foreach my $line (split/\n/, $iptables) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if (($line =~ /-m $protocol /) && ($line =~ /--dport $port_number /) && ($line =~ /ACCEPT/)) - { - $open = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open }}); - last; - } - } - - # If the port is open and the task is 'check' or 'open', we're done and can return now and save a lot - # of time. - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'task' => $task, 'open' => $open }}); - if ((($task eq "check") or ($task eq "open")) && ($open)) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open }}); - return($open); - } - - # Make sure firewalld is running. - my $firewalld_running = $anvil->System->check_daemon({daemon => $anvil->data->{sys}{daemon}{firewalld}}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { firewalld_running => $firewalld_running }}); - if (not $firewalld_running) - { - if ($anvil->data->{sys}{daemons}{restart_firewalld}) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0127"}); - my $return_code = $anvil->System->start_daemon({daemon => $anvil->data->{sys}{daemon}{firewalld}}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { return_code => $return_code }}); - if ($return_code) - { - # non-0 means something went wrong. - return("!!error!!"); - } - } - else - { - # We've been asked to leave it off. - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0128"}); - return(0); - } - } - - - # Before we do anything, what zone is active? - my $active_zone = ""; - if (not $active_zone) - { - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --get-active-zones"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); - foreach my $line (split/\n/, $output) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); - if ($line !~ /\s/) - { - $active_zone = $line; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_zone => $active_zone }}); - } - last; - } - } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_zone => $active_zone }}); - - # If I still don't know what the active zone is, we're done. - if (not $active_zone) - { - return("!!error!!"); - } - - # If we have an active zone, see if the requested port is open. - my $zone_file = $anvil->data->{path}{directories}{firewalld_zones}."/".$active_zone.".xml"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { zone_file => $zone_file }}); - if (not -e $zone_file) - { - #... - return($open); - } - - # Read the XML to see what services are opened already and translate those into port numbers and - # protocols. - local $@; - my $open_services = []; - my $xml = XML::Simple->new(); - my $body = ""; - my $test = eval { $body = $xml->XMLin($zone_file, KeyAttr => { language => 'name', key => 'name' }, ForceArray => [ 'service' ]) }; - if (not $test) - { - chomp $@; - my $error = "[ Error ] - The was a problem reading: [$zone_file]. The error was:\n"; - $error .= "===========================================================\n"; - $error .= $@."\n"; - $error .= "===========================================================\n"; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", raw => $error}); - - # Clear the error so it doesn't propogate out to a future 'die' and confuse things. - $@ = ''; - } - else - { - # Parse the already-opened services - foreach my $hash_ref (@{$body->{service}}) - { - # Load the details of this service. - my $service = $hash_ref->{name}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); - $anvil->System->_load_specific_firewalld_zone({service => $hash_ref->{name}}); - push @{$open_services}, $service; - } - - # Now loop through the open services, protocols and ports looking for the one passed in by - # the caller. If found, the port is already open. - foreach my $service (sort {$a cmp $b} @{$open_services}) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); - foreach my $this_protocol ("tcp", "udp") - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_protocol => $this_protocol }}); - foreach my $this_port (sort {$a cmp $b} @{$anvil->data->{firewalld}{zones}{by_name}{$service}{tcp}}) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_port => $this_port }}); - if (($port_number eq $this_port) && ($this_protocol eq $protocol)) - { - # Opened already (as the recorded service). - $open = $service; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open }}); - last if $open; - } - last if $open; - } - last if $open; - } - last if $open; - } - } - - # We're done if we were just checking. However, if we've been asked to open a currently closed port, - # or vice versa, make the change before returning. - my $changed = 0; - if (($task eq "open") && (not $open)) - { - # Map the port to a service, if possible. - my $service = $anvil->System->_match_port_to_service({port => $port_number}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); - - # Open the port - if ($service) - { - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --add-service ".$service; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); - if ($output eq "success") - { - $open = 1; - $changed = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open, changed => $changed }}); - } - else - { - # Something went wrong... - return("!!error!!"); - } - } - else - { - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --add-port ".$port_number."/".$protocol; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); - if ($output eq "success") - { - $open = 1; - $changed = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open, changed => $changed }}); - } - else - { - # Something went wrong... - return("!!error!!"); - } - } - } - elsif (($task eq "close") && ($open)) - { - # Map the port to a service, if possible. - my $service = $anvil->System->_match_port_to_service({port => $port_number}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { service => $service }}); - - # Close the port - if ($service) - { - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --remove-service ".$service; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); - if ($output eq "success") - { - $open = 0; - $changed = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open, changed => $changed }}); - } - else - { - # Something went wrong... - return("!!error!!"); - } - } - else - { - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --remove-port ".$port_number."/".$protocol; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }}); - if ($output eq "success") - { - $open = 0; - $changed = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open, changed => $changed }}); - } - else - { - # Something went wrong... - return("!!error!!"); - } - } - } - - # If we made a change, reload. - if ($changed) - { - $anvil->System->reload_daemon({daemon => $anvil->data->{sys}{daemon}{firewalld}}); - } - - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'open' => $open }}); - return($open); -} - =head2 pids This parses C<< ps aux >> and stores the information about running programs in C<< pids:::: >>. If called against a remote host, the data is stored in C<< remote_pids:::: >>. diff --git a/notes b/notes index aee41508..6d5aab04 100644 --- a/notes +++ b/notes @@ -7,6 +7,24 @@ dnf -y update && dnf -y install https://www.alteeve.com/an-repo/m3/anvil-release dnf -y update && dnf -y install https://www.alteeve.com/an-repo/m3/anvil-release-latest.noarch.rpm && alteeve-repo-setup -y && dnf -y install anvil-dr --allowerasing +### Currently set default zone; +# Doesn't seem to matter - /etc/firewalld/firewalld.conf:6:DefaultZone=public + +firewall-cmd --get-default-zone +# public +firewall-cmd --permanent --set-default-zone=IFN1 + + +firewall-cmd --permanent --new-zone="IFN1" +firewall-cmd --permanent --zone=IFN1 --set-description="Internet-Facing Network 1" +firewall-cmd --permanent --zone=IFN1 --set-short="IFN1" +firewall-cmd --permanent --zone=IFN1 --add-interface=ifn1_bond1 +firewall-cmd --permanent --zone=IFN1 --add-service=ssh +firewall-cmd --permanent --zone=IFN1 --add-service=postgresql +firewall-cmd --permanent --zone=IFN1 --add-port=22869/tcp +firewall-cmd --reload + + # Configure APC PDUs and UPSes tcpip -i 10.201.2.3 -s 255.255.0.0 -g 10.201.255.254 web -h enable @@ -15,6 +33,13 @@ snmp -S enable -c1 private -a1 writeplus snmp -S enable -c2 public -a2 writeplus +/root/ci-tools/ci-destroy-anvil-bm-vm +/root/ci-tools/ci-setup-anvil-bm-vm rhel-8 ci ci + +watch 'echo "striker 1"; ssh root@an-striker01 "grep ^database /etc/anvil/anvil.conf | grep host"; echo "striker 2"; ssh root@an-striker02 "grep ^database /etc/anvil/anvil.conf | grep host"; echo "node 1"; ssh root@an-a01n01 "grep ^database /etc/anvil/anvil.conf | grep host"; echo "node 2"; ssh root@an-a01n02 "grep ^database /etc/anvil/anvil.conf | grep host"; echo "dr 1"; ssh root@an-a01dr01 "grep ^database /etc/anvil/anvil.conf | grep host";' + + + Anvil! to Anvil! live migration; 1. Create LVs 2. Make sure /etc/hosts is populated @@ -66,18 +91,6 @@ export python=python3 ============ -DISABLE KSM! -- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_tuning_and_optimization_guide/chap-ksm - -NEXT; - - -RHEL 8 package changes: -chrony replaces ntp -cockpit replaces virt-manager (deprecated) -e1000e replaces e1000 driver -tmux replaces screen -iproute2 replaces bridge-utils (See "ip link" and man bridge.) - ==] UEFI Setup [====================================================================== ignoredisk --only-use=vdb,vda @@ -120,6 +133,9 @@ x = Network; - SN = 100 + network ie: SN1 = 10.101.y.z SN2 = 10.102.y.z + - MN = 199 (only 1, always back-to-back between nodes 1 and 2) + ie: MN1 = 10.199.y.z + y = Device Type. Foudation Pack; @@ -149,14 +165,29 @@ RHEL 8 Firewall ============================= +### Nodes +* BCN Ports +TCP 22 sshd +TCP 2224 pcsd It is crucial to open port 2224 in such a way that pcs from any node can talk to all nodes in the cluster, including itself. +UDP 5404 corosync Required on corosync nodes if corosync is configured for multicast UDP +UDP 5405 corosync Required on all corosync nodes (needed by corosync) +TCP 5900+ vnc +TCP 49152-49215 virsh live migration - migration_port_min and migration_port_max attributes in the /etc/libvirt/qemu.conf + +* SN Ports (Pull ports from DRBD resource config) +TCP 7788+ drbd 1 port per resource + +* IFN Ports +TCP 22 sshd + + +MN Ports +TCP 49152-49215 virsh mn live migration - migration_port_min and migration_port_max attributes in the /etc/libvirt/qemu.conf + + Ports we care about Porto Number Used by Nets Description -TCP 2224 pcsd bcn It is crucial to open port 2224 in such a way that pcs from any node can talk to all nodes in the cluster, including itself. -UDP 5404 corosync bcn Required on corosync nodes if corosync is configured for multicast UDP -UDP 5405 corosync bcn Required on all corosync nodes (needed by corosync) -TCP 7788+ drbd sn 1 port per resource -TCP 49152-49215 virsh bcn live migration - migration_port_min and migration_port_max attributes in the /etc/libvirt/qemu.conf NOTE: DHCP listens to raw sockets and ignores firewalld rules. We need to stop dhcpd directly - https://kb.isc.org/docs/aa-00378 diff --git a/ocf/alteeve/server b/ocf/alteeve/server index e8aa4dff..d5a4a225 100755 --- a/ocf/alteeve/server +++ b/ocf/alteeve/server @@ -260,7 +260,11 @@ if ($anvil->data->{switches}{migrate_to}) } elsif ($anvil->data->{switches}{migrate_from}) { - # This is called after a migration is complete, so we're basically just doing a status check. + # This is called after a migration is complete, so we're basically just doing a status check. In case + # the TCP port isn't open in the firewall yet though, we'll check that now. + $anvil->Network->manage_firewall(); + + # Now do the status check. $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0529", variables => { server => $anvil->data->{environment}{OCF_RESKEY_name} }}); server_status($anvil); } @@ -748,6 +752,9 @@ sub start_drbd_resource } # If auto-promote isn't set, promote the resource. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "drbd::config::${local_host}::auto-promote" => $anvil->data->{drbd}{config}{$local_host}{'auto-promote'}, + }}); if (not $anvil->data->{drbd}{config}{$local_host}{'auto-promote'}) { foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$local_host}{$server}{resource}}) @@ -756,6 +763,7 @@ sub start_drbd_resource server => $server, resource => $resource, }}); + # Make the local resource primary. $anvil->DRBD->manage_resource({ resource => $resource, @@ -765,39 +773,42 @@ sub start_drbd_resource } } + ### NOTE: We always check the peer now, in case it's resource is down and ours happens to be up. # See if we're inconsistent and, if so, if we can connect our peers. - sleep 2; - $anvil->DRBD->get_status({debug => 3}); - my $peer_startup_needed = 1; - foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$local_host}{$server}{resource}}) + if (0) { - # Is the current resource up locally already? - my $role = defined $anvil->data->{drbd}{status}{$host}{resource}{$resource}{role} ? $anvil->data->{drbd}{status}{$host}{resource}{$resource}{role} : ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - 's1:resource' => $resource, - 's2:role' => $role, - }}); - - # Check all volumes. - foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{drbd}{status}{$host}{resource}{$resource}{devices}{volume}}) + sleep 2; + $anvil->DRBD->get_status({debug => 3}); + my $peer_startup_needed = 1; + foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$local_host}{$server}{resource}}) { - my $disk_state = defined $anvil->data->{drbd}{status}{$host}{resource}{$resource}{devices}{volume}{$volume}{'disk-state'} ? $anvil->data->{drbd}{status}{$host}{resource}{$resource}{devices}{volume}{$volume}{'disk-state'} : ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { disk_state => $disk_state }}); - - if ((lc($disk_state) eq "consistent") or - (lc($disk_state) eq "outdated") or - (lc($disk_state) eq "failed") or - (not $disk_state)) + # Is the current resource up locally already? + my $role = defined $anvil->data->{drbd}{status}{$host}{resource}{$resource}{role} ? $anvil->data->{drbd}{status}{$host}{resource}{$resource}{role} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:resource' => $resource, + 's2:role' => $role, + }}); + + # Check all volumes. + foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{drbd}{status}{$host}{resource}{$resource}{devices}{volume}}) { - # This will trigger trying to ssh into peer(s) and up'ing their resource. - $peer_startup_needed = 0; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { peer_startup_needed => $peer_startup_needed }}); - last; + my $disk_state = defined $anvil->data->{drbd}{status}{$host}{resource}{$resource}{devices}{volume}{$volume}{'disk-state'} ? $anvil->data->{drbd}{status}{$host}{resource}{$resource}{devices}{volume}{$volume}{'disk-state'} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { disk_state => $disk_state }}); + + if ((lc($disk_state) eq "consistent") or + (lc($disk_state) eq "outdated") or + (lc($disk_state) eq "failed") or + (not $disk_state)) + { + # This will trigger trying to ssh into peer(s) and up'ing their resource. + $peer_startup_needed = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { peer_startup_needed => $peer_startup_needed }}); + last; + } } } } - ### NOTE: We always check the peer now, in case it's resource is down and ours happens to be up. # Do we need to start the resource on our peers? #$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { peer_startup_needed => $peer_startup_needed }}); #if (not $peer_startup_needed) @@ -806,7 +817,7 @@ sub start_drbd_resource # return(0); #} - # Start DRBD on the peer(s). + # Start DRBD on the peer(s), if we can. foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$local_host}{$server}{resource}}) { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { resource => $resource }}); @@ -867,7 +878,7 @@ sub start_drbd_resource my $all_resources_ok = 1; foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$local_host}{$server}{resource}}) { - # This is set to '1' is either the volumes are UpToDate or Sync'ing. + # This is set to '1' if either the volumes are UpToDate or Sync'ing. $anvil->data->{drbd}{status}{$local_host}{resource}{$resource}{ok} = 0; foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{drbd}{status}{$local_host}{resource}{$resource}{devices}{volume}}) { @@ -878,7 +889,7 @@ sub start_drbd_resource $disk_state = "" if not defined $disk_state; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { disk_state => $disk_state }}); - if ($disk_state ne "uptodate") + if ($disk_state eq "uptodate") { $anvil->data->{drbd}{status}{$local_host}{resource}{$resource}{devices}{volume}{$volume}{ok} = 1; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -892,7 +903,7 @@ sub start_drbd_resource # See if we're a SyncTarget foreach my $connection (sort {$a cmp $b} keys %{$anvil->data->{drbd}{status}{$local_host}{resource}{$resource}{connection}}) { - my $connection_state = $anvil->data->{drbd}{status}{$local_host}{resource}{$resource}{connection}{'connection-state'}; + my $connection_state = $anvil->data->{drbd}{status}{$local_host}{resource}{$resource}{connection}{$connection}{'connection-state'}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { connection => $connection, connection_state => $connection_state, @@ -948,6 +959,7 @@ sub start_drbd_resource } } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { all_resources_ok => $all_resources_ok }}); if ($all_resources_ok) { $waiting = 0; diff --git a/scancore-agents/Makefile.am b/scancore-agents/Makefile.am index 42243270..6db8ee20 100644 --- a/scancore-agents/Makefile.am +++ b/scancore-agents/Makefile.am @@ -66,6 +66,11 @@ dist_lvm_DATA = \ dist_lvm_SCRIPTS = \ scan-lvm/scan-lvm +networkdir = ${targetdir}/scan-network +dist_network_DATA = \ + scan-network/scan-network.sql \ + scan-network/scan-network.xml + serverdir = ${targetdir}/scan-server dist_server_DATA = \ scan-server/scan-server.sql \ diff --git a/scancore-agents/scan-server/scan-server b/scancore-agents/scan-server/scan-server index 7e2824c0..d61de45f 100755 --- a/scancore-agents/scan-server/scan-server +++ b/scancore-agents/scan-server/scan-server @@ -311,7 +311,7 @@ sub collect_data * Server states; running - The domain is currently running on a CPU idle - The domain is idle, and not running or runnable. This can be caused because the domain is waiting on IO (a traditional wait state) or has gone to sleep because there was nothing else for it to do. -paused - The domain has been paused, usually occurring through the administrator running virsh suspend. When in a paused state the domain will still consume allocated resources like memory, but will not be eligible for scheduling by the hypervisor. +paused - The domain has been paused. This can happen when a server is migrating to this host, or through the administrator running virsh suspend. When in a paused state the domain will still consume allocated resources like memory, but will not be eligible for scheduling by the hypervisor. in shutdown - The domain is in the process of shutting down, i.e. the guest operating system has been notified and should be in the process of stopping its operations gracefully. shut off - The domain is not running. Usually this indicates the domain has been shut down completely, or has not been started. crashed - The domain has crashed, which is always a violent ending. Usually this state can only occur if the domain has been configured not to restart on crash. @@ -419,6 +419,9 @@ DELETED - Marks a server as no longer existing }); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { server_definition_uuid => $server_definition_uuid }}); + # Make sure the firewall is updated. + $anvil->Network->manage_firewall(); + # Reload the servers. $anvil->Database->get_servers(); $anvil->Database->get_server_definitions(); @@ -701,6 +704,14 @@ DELETED - Marks a server as no longer existing server_boot_time => $server_boot_time." (".$anvil->Get->date_and_time({use_time => $server_boot_time}).")", }}); } + + # If the server was migrated or booted, update the firewall. With luck, this happened + # already, so this is a back-stop mainly. + if (($old_server_state eq "migrating") or ($old_server_state eq "shut off")) + { + # Make sure the firewall is updated. + $anvil->Network->manage_firewall(); + } } if ($server_boot_time ne $old_server_boot_time) { diff --git a/share/words.xml b/share/words.xml index 6dd9c427..e2584362 100644 --- a/share/words.xml +++ b/share/words.xml @@ -1320,6 +1320,11 @@ Note: This will connect the DR host until the disk(s) on DR are (all) UpToDate. Still sync'ing from: [#!variable!sync_source!#] at a rate of: [#!variable!sync_speed!#/sec]. Estimated time remaining is: [#!variable!time_to_sync!#]. Sync'ed! Bringing the resource back down now. Waiting for the connection to come up... + Manage Firewall + This will wait for the named server to appear, then update the firewall to ensure needed ports are open for access to the server's desktop. + Waiting until the server: [#!variable!server!#] appears. + [ Error ] - Timed out waiting for the server: [#!variable!server!#] to appear! + Waiting for the server: [#!variable!server!#] to appear. Will wait: [#!variable!time_left!#] more seconds. Starting: [#!variable!program!#]. @@ -2090,7 +2095,7 @@ The file: [#!variable!file!#] needs to be updated. The difference is: query() was asked to query the database with UUID: [#!variable!old_uuid!#] but there is no file handle open to the database. Switched the read to: [#!variable!new_uuid!#].]]> Opening the firewall zone: [#!variable!zone!#] to allow the service: [#!variable!service!#]. No password for the database on the host with UUID: [#!variable!uuid!#], skipping it. - The firewalld daemon isn't running, skipping firewall setup. + The firewalld daemon isn't running, skipping firewall setup. Is 'sys::daemon::firewalld' set to '0' in anvil.conf? The postgresql server is installed. The host: [#!variable!host_name!#] was powered off for an unknown reason, and 'feature::scancore::disable::boot-unknown-stop' is set to: [#!data!feature::scancore::disable::boot-unknown-stop!#]. Will not boot this host. The host: [#!variable!host_name!#] was powered off for an unknown reason, and 'feature::scancore::disable::boot-unknown-stop' is set to: [#!data!feature::scancore::disable::boot-unknown-stop!#]. If power and temperature looks good, we'll boot it. @@ -2127,6 +2132,17 @@ The file: [#!variable!file!#] needs to be updated. The difference is: The timestamp has been updated from: [#!variable!old_time!#] to: [#!variable!new_time!#]. read_state() was called but both the 'state_name' and 'state_uuid' parameters were not passed or both were empty.]]> Forcing the dailing resync and checking to clear records in the history schema no longer in public schema. + Updating the OUI list will happen after the system has been up for at least an hour. You can force an update now by running 'striker-parse-oui --force' at the command line. + Updated: [#!data!path::configs::firewalld.conf!#] to disable 'AllowZoneDrifting'. See: https://firewalld.org/2020/01/allowzonedrifting + Created the firewall zone: [#!variable!zone!#]. + Added the interface: [#!variable!interface!#] to the firewall zone: [#!variable!zone!#]. + Opening the firewall service: [#!variable!service!#] for the zone: [#!variable!zone!#]! + Closing the firewall service: [#!variable!service!#] for the zone: [#!variable!zone!#]! + Opening the firewall port: [#!variable!port!#/#!variable!protocol!#] for the zone: [#!variable!zone!#]! + Opening the firewall port range: [#!variable!port!#/#!variable!protocol!#] for the zone: [#!variable!zone!#]! + Closing the firewall port: [#!variable!port!#/#!variable!protocol!#] for the zone: [#!variable!zone!#]! + Closing the firewall port range: [#!variable!port!#/#!variable!protocol!#] for the zone: [#!variable!zone!#]! + Changes were made to the firewall, reloading now. The host name: [#!variable!target!#] does not resolve to an IP address. @@ -2548,6 +2564,11 @@ Available options; #!variable!cores!#c (#!variable!threads!#t) -=] Server Usage and Anvil! Node Resource Availability This program is currently disabled, please see NOTE in the header for more information. + # NOTE: This was added by the Anvil!, as per firewalld's warning below. +# WARNING: AllowZoneDrifting is enabled. This is considered an insecure +# configuration option. It will be removed in a future release. +# Please consider disabling it now. + Migration Network Saved the mail server information successfully! @@ -3225,6 +3246,17 @@ We will sleep a bit and try again. [ Warning ] - While evaluating database shutdown, the host UUID: [#!variable!host_uuid!#] was not yet found in the database on host: [#!variable!db_uuid!#]. DB shutdown will not happen until all hosts are in all DBs. [ Warning ] - While preparing to record the state: [#!variable!state_info!#], the host UUID: [#!variable!host_uuid!#] was not yet found in the database on host: [#!variable!db_uuid!#]. NOT recording the state! [ Warning ] - The daemon: [#!variable!daemon!#] was found running. It shouldn't be, and will now be stopped and disabled. + [ Warning ] - Failed to parse the firewall zone file: [#!variable!file!#]. The body of the file was: +======== +#!variable!body!# +======== + +The error was: + +======== +#!variable!error!# +======== + diff --git a/tools/Makefile.am b/tools/Makefile.am index 18dd6e5d..ca8eec50 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -17,8 +17,10 @@ dist_sbin_SCRIPTS = \ anvil-get-server-screenshot \ anvil-join-anvil \ anvil-maintenance-mode \ + anvil-manage-dr \ anvil-manage-files \ anvil-manage-firewall \ + anvil-manage-host \ anvil-manage-keys \ anvil-manage-power \ anvil-manage-server \ @@ -26,16 +28,25 @@ dist_sbin_SCRIPTS = \ anvil-parse-fence-agents \ anvil-provision-server \ anvil-rename-server \ + anvil-report-usage \ anvil-safe-start \ + anvil-safe-stop \ anvil-scan-network \ + anvil-show-local-ips \ anvil-shutdown-server \ anvil-sync-shared \ + anvil-test-alerts \ + anvil-update-definition \ anvil-update-issue \ anvil-update-states \ anvil-update-system \ anvil-watch-bonds \ scancore \ + striker-auto-initialize-all \ striker-boot-machine \ + striker-db-report \ + striker-db-status \ + striker-file-manager \ striker-get-peer-data \ striker-initialize-host \ striker-manage-install-target \ @@ -47,10 +58,8 @@ dist_sbin_SCRIPTS = \ striker-prep-database \ striker-purge-target \ striker-scan-network \ - striker-show-db-counts \ - striker-auto-initialize-all \ - striker-db-status - + striker-show-db-counts + fencedir = ${FASEXECPREFIX}/sbin dist_fence_SCRIPTS = \ diff --git a/tools/anvil-daemon b/tools/anvil-daemon index 0caae802..3b4991c9 100755 --- a/tools/anvil-daemon +++ b/tools/anvil-daemon @@ -698,8 +698,8 @@ sub handle_periodic_tasks # Now check to see if it's time to run daily tasks. if ($now_time >= $anvil->data->{timing}{next_daily_check}) { - # Make sure ksm and ksmtuned are disabled. - foreach my $daemon ("ksm.service", "ksmtuned.service") + # Make sure ksm, ksmtuned and tuned are disabled. + foreach my $daemon ("ksm.service", "ksmtuned.service", "tuned.service") { my $status = $anvil->System->check_daemon({daemon => $daemon}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -977,6 +977,9 @@ sub run_once { my ($anvil) = @_; + # Make sure the firewall is configured. + $anvil->Network->manage_firewall(); + # Check that the database is ready. prep_database($anvil); @@ -985,7 +988,7 @@ sub run_once # Check the ssh stuff. # NOTE: This actually runs again in the minutes tasks, but needs to run on boot as well. - $anvil->System->check_ssh_keys({debug => 2}); + $anvil->System->check_ssh_keys(); # Check setuid wrappers check_setuid_wrappers($anvil); diff --git a/tools/anvil-manage-firewall b/tools/anvil-manage-firewall index 64ba4075..54a8b9b6 100755 --- a/tools/anvil-manage-firewall +++ b/tools/anvil-manage-firewall @@ -9,9 +9,6 @@ # 2 = Failed to write or update a file. # # TODO: -# - TEMP: During development, firewalling is disabled. -# - Add support for enabling/disabling MASQ'ing the BCN -# - Add support for listening for NTP queries based on /etc/chrony.conf's Server entries (map them to networks / zones). # # # Allow routing/masq'ing through the IFN1 (provide net access to the BCN) # firewall-cmd --zone=IFN1 --add-masquerade @@ -51,28 +48,48 @@ if (not $anvil->data->{sys}{manage}{firewall}) $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }}); # Read switches -$anvil->data->{switches}{'y'} = ""; +$anvil->data->{switches}{'job-uuid'} = ""; +$anvil->data->{switches}{server} = ""; $anvil->Get->switches; +$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 'switches::job-uuid' => $anvil->data->{switches}{'job-uuid'}, + 'switches::server' => $anvil->data->{switches}{server}, +}}); -# For now, we just disable the firewall, if it is enabled. -my $firewall_running = $anvil->System->check_daemon({daemon => "firewalld", debug => 3}); -$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { firewall_running => $firewall_running }}); -if ($firewall_running eq "1") -{ - # Disable it. - $anvil->System->stop_daemon({daemon => "firewalld", debug => 2}); - $anvil->System->disable_daemon({daemon => "firewalld", debug => 2}); -} -$anvil->nice_exit({exit_code => 0}); +# Log our start. +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0134"}); +# If we've been passed a job UUID, pick up the details. +if ($anvil->data->{switches}{'job-uuid'}) +{ + $anvil->Job->clear(); + $anvil->Job->get_job_details(); + $anvil->Job->update_progress({ + progress => 1, + job_picked_up_by => $$, + job_picked_up_at => time, + message => "message_0134", + }); + + if ($anvil->data->{jobs}{job_data} =~ /server=(.*)$/) + { + $anvil->data->{switches}{server} = $1 if not $anvil->data->{switches}{server}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 'switches::server' => $anvil->data->{switches}{server}, + }}); + wait_for_server($anvil); + } -$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "message_0134"}); -check_initial_setup($anvil); +} -# Restart, if needed. -if ($anvil->data->{firewall}{reload}) +# This used to do all the work, but that's now moved to the method below. So all we do here now is call it. +$anvil->Network->manage_firewall(); +if ($anvil->data->{switches}{'job-uuid'}) { - restart_firewall($anvil); + $anvil->Job->update_progress({ + progress => 100, + message => "job_0281", + }); } # We're done @@ -83,284 +100,71 @@ $anvil->nice_exit({exit_code => 0}); # Private functions. # ############################################################################################################# -sub check_initial_setup +# This simple watches 'virsh list' until the named server appears. +sub wait_for_server { - my ($anvil) = @_; - - # See what we've found... We'll look at what 'check_firewall' finds later to know if any unused zones - # need to be removed. - my $needed_zones = []; + ($anvil) = @_; - # This will get set if we need to restart the firewalld daemon. - $anvil->data->{firewall}{reload} = 0; - - # Get a list of networks. - $anvil->Network->get_ips({debug => 3}); - - # Get the list of existing zones from iptables/firewalld. - $anvil->System->check_firewall({debug => 3}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "firewall::default_zone" => $anvil->data->{firewall}{default_zone} }}); - - my $internet_zone = ""; - my $local_host = $anvil->Get->short_host_name(); - foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$local_host}{interface}}) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { interface => $interface }}); - if ($interface =~ /^((bcn|ifn|sn)\d+)_/) - { - # We'll use the start of the string (network type) as the zone, though it should - # always be overridden by the ZONE="" variable in each interface's config. - my $zone = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { zone => $zone }}); - if ((exists $anvil->data->{network}{$local_host}{interface}{$interface}{variable}{ZONE}) && ($anvil->data->{network}{$local_host}{interface}{$interface}{variable}{ZONE})) - { - $zone = $anvil->data->{network}{$local_host}{interface}{$interface}{variable}{ZONE}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { zone => $zone }}); - } - push @{$needed_zones}, $zone; - - $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{ip} = $anvil->data->{network}{$local_host}{interface}{$interface}{ip}; - $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{subnet_mask} = $anvil->data->{network}{$local_host}{interface}{$interface}{subnet_mask}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "firewall::zone::${zone}::interface::${interface}::ip" => $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{ip}, - "firewall::zone::${zone}::interface::${interface}::subnet_mask" => $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{subnet_mask}, - "network::${local_host}::interface::${interface}::default_gateway" => $anvil->data->{network}{$local_host}{interface}{$interface}{default_gateway}, - }}); - - if ($anvil->data->{network}{$local_host}{interface}{$interface}{default_gateway}) - { - $internet_zone = $zone; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { internet_zone => $internet_zone }}); - - if ((not $anvil->data->{firewall}{default_zone}) or ($anvil->data->{firewall}{default_zone} eq "public")) - { - $anvil->data->{firewall}{default_zone} = $zone; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "firewall::default_zone" => $anvil->data->{firewall}{default_zone} }}); - } - - } - } - } - - # Process the list of existing zones from iptables/firewalld. - foreach my $zone (sort {$a cmp $b} keys %{$anvil->data->{firewall}{zone}}) + $anvil->Job->update_progress({ + progress => 25, + message => "job_0401,!!server!".$anvil->data->{switches}{server}."!!", + }); + my $wait_until = time + 60; + my $waiting = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + wait_until => $wait_until, + }}); + while($waiting) { - my $file = exists $anvil->data->{firewall}{zone}{$zone}{file} ? $anvil->data->{firewall}{zone}{$zone}{file} : $anvil->data->{path}{directories}{firewalld_zones}."/".$zone.".xml"; - my $user_file = $anvil->data->{path}{directories}{firewalld_zones_etc}."/".$zone.".xml"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "s1:zone" => $zone, - "s2:file" => $file, - "s3:user_file" => $user_file, - }}); - - ### NOTE: This is probably overkill. - # Is this a zone I want/need? - my $wanted = 0; - foreach my $needed_zone (sort {$a cmp $b} @{$needed_zones}) - { - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "s1:zone" => $zone, - "s2:needed_zone" => $needed_zone, - }}); - if ($needed_zone eq $zone) - { - $wanted = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { wanted => $wanted }}); - last; - } - } - - # Skip if this is a zone I don't care about. - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { wanted => $wanted }}); - next if not $wanted; - - # Now, skip if the user-land file exists. - if (-e $user_file) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0143", variables => { zone => $zone, file => $user_file }}); - next; - } - - # Create or update the zone file, if needed. - my $template = ""; - my $description = ""; - if ($zone =~ /bcn(\d+)/i) - { - my $number = $1; - $template = "bcn_zone"; - $description = $anvil->Words->string({key => "message_0131", variables => { number => $number }}); - } - elsif ($zone =~ /sn(\d+)/i) - { - my $number = $1; - $template = "sn_zone"; - $description = $anvil->Words->string({key => "message_0132", variables => { number => $number }}); - } - elsif ($zone =~ /ifn(\d+)/i) - { - my $number = $1; - $template = "ifn_zone"; - $description = $anvil->Words->string({key => "message_0133", variables => { number => $number }}); - } - else - { - # This should never be hit, but it's a fail-safe in we're in a zone we don't manage. - next; - } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "s1:template" => $template, - "s2:description" => $description, - }}); + my $shell_call = $anvil->data->{path}{exe}{virsh}." list --name"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { shell_call => $shell_call }}); - my $new_zone_body = $anvil->Template->get({debug => 3, file => "firewall.txt", show_name => 0, name => $template, variables => { - zone => $zone, - description => $description, + my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { + 's1:output' => $output, + 's2:return_code' => $return_code, }}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { new_zone_body => $new_zone_body }}); - - # This is another fail safe, don't edit unless we have a new file body. - if (not $new_zone_body) - { - next; - } - # If there isn't a body, see if the file exists. If it doesn't, create it. If it does, read it. - my $update_file = 0; - my $old_zone_body = exists $anvil->data->{firewall}{zone}{$zone}{body} ? $anvil->data->{firewall}{zone}{$zone}{body} : ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { old_zone_body => $old_zone_body }}); - if (-e $file) + foreach my $server (split/\n/, $output) { - # Has it changed? - my $diff = diff \$old_zone_body, \$new_zone_body, { STYLE => 'Unified' }; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { diff => $diff }}); - if ($diff) + $server = $anvil->Words->clean_spaces({string => $server}); + next if not $server; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { server => $server }}); + + if ($server eq $anvil->data->{switches}{server}) { - # Update it - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0136", variables => { zone => $zone, file => $file }}); - $update_file = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update_file => $update_file }}); + # Found it. + $waiting = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { waiting => $waiting }}); } } - else - { - # Create it - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0137", variables => { zone => $zone, file => $file }}); - $update_file = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update_file => $update_file }}); - } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update_file => $update_file }}); - if ($update_file) + if ($waiting) { - my $error = $anvil->Storage->write_file({ - file => $file, - body => $new_zone_body, - group => "root", - user => "root", - mode => "0644", - overwrite => 1, - }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { error => $error }}); - - if ($error) + if (time > $wait_until) { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0043", variables => { file => $file }}); - $anvil->nice_exit({exit_code => 2}); + # timed out + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, priority => "err", key => "job_0402", variables => { server => $anvil->data->{switches}{server} }}); + $anvil->Job->update_progress({ + progress => 75, + message => "job_0402,!!server!".$anvil->data->{switches}{server}."!!", + }); } else { - # We need an immediate reload to pick up the new file. - restart_firewall($anvil); - } - } - - # Make sure the appropriate interfaces are in this zone. - foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{firewall}{zone}{$zone}{interface}}) - { - my $in_zone = exists $anvil->data->{firewall}{interface}{$interface}{zone} ? $anvil->data->{firewall}{interface}{$interface}{zone} : ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "s1:interface" => $interface, - "s2:in_zone" => $in_zone, - "s3:zone" => $zone, - }}); - - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { in_zone => $in_zone, zone => $zone }}); - if ((not $in_zone) or ($zone ne $in_zone)) - { - # Add it - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0138", variables => { - interface => $interface, - zone => $zone, + sleep 3; + my $time_left = $wait_until - time; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "job_0403", variables => { + server => $anvil->data->{switches}{server}, + time_left => $time_left, }}); - - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --zone=".$zone." --change-interface=".$interface." --permanent"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - my ($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }}); - - $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --zone=".$zone." --change-interface=".$interface; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - ($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }}); - - $anvil->data->{firewall}{reload} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "firewall::reload" => $anvil->data->{firewall}{reload} }}); + $anvil->Job->update_progress({ + progress => 50, + message => "job_0403,!!server!".$anvil->data->{switches}{server}."!!,!!time_left!".$time_left."!!", + }); } - - # Delete it so we know this one has been processed. - delete $anvil->data->{firewall}{interface}{$interface}; - } - } - - # Do we need to update the default zone? - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - internet_zone => $internet_zone, - "firewall::default_zone" => $anvil->data->{firewall}{default_zone}, - }}); - if ($anvil->data->{firewall}{default_zone}) - { - # What's the current default zone? - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --get-default-zone"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - my ($default_zone, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { default_zone => $default_zone, return_code => $return_code }}); - - if ($default_zone ne $anvil->data->{firewall}{default_zone}) - { - # Update. - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0141", variables => { zone => $internet_zone }}); - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --set-default-zone=".$anvil->data->{firewall}{default_zone}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - my ($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }}); - - $anvil->data->{firewall}{reload} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "firewall::reload" => $anvil->data->{firewall}{reload} }}); } } - # NOTE: We may want to do machine-specific stuff down the road. - my $type = $anvil->Get->host_type(); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { type => $type }}); - - return(0); -} - -sub restart_firewall -{ - my ($anvil) = @_; - - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0139"}); - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --complete-reload"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }}); - - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "message_0140"}); - $anvil->System->restart_daemon({debug => 3, daemon => "firewalld"}); - - $anvil->data->{firewall}{reload} = 0; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "firewall::reload" => $anvil->data->{firewall}{reload} }}); - return(0); -} +} \ No newline at end of file diff --git a/tools/anvil-migrate-server b/tools/anvil-migrate-server index 488b939d..52348b40 100755 --- a/tools/anvil-migrate-server +++ b/tools/anvil-migrate-server @@ -7,6 +7,10 @@ # 0 = Normal exit. # 1 = Any problem that causes an early exit. # +# NOTE: as per qemu.conf defaults, a maximum of 63 live migrations can happen at the same time. We need to +# to count how many servers there are and, if the number is over 63, update qemu.conf. The current +# range of ports available for live migration can be found here: +# - my ($migration_minimum, $migration_maximum) = $anvil->Network->_get_live_migration_ports(); use strict; use warnings; diff --git a/tools/anvil-provision-server b/tools/anvil-provision-server index 641cc830..72b38694 100755 --- a/tools/anvil-provision-server +++ b/tools/anvil-provision-server @@ -308,6 +308,9 @@ sub run_jobs # Create the DRBD resource file create_resource_file($anvil); + # Make sure the DRBD ports are open. + $anvil->Network->manage_firewall(); + # Create the DRBD metadata, if needed. $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'job::initialize_drbd' => $anvil->data->{job}{initialize_drbd} }}); create_md($anvil) if $anvil->data->{job}{initialize_drbd}; @@ -328,6 +331,9 @@ sub run_jobs provision_server($anvil); } + # Make sure the VNC port is open. + $anvil->Network->manage_firewall(); + # Add the server to the cluster. add_server_to_cluster($anvil); diff --git a/tools/striker-parse-oui b/tools/striker-parse-oui index 45dfe336..17a852a8 100755 --- a/tools/striker-parse-oui +++ b/tools/striker-parse-oui @@ -152,11 +152,24 @@ sub check_if_time my ($anvil) = @_; # NOTE: We only scan once a day, unless 'force' is used. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "switches::force" => $anvil->data->{switches}{force} }}); if ($anvil->data->{switches}{force}) { return(0); } - elsif (not $anvil->data->{switches}{'job-uuid'}) + + # What's the uptime? If the uptime is less than an hour, don't run. + my $uptime = $anvil->Get->uptime; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { uptime => $uptime }}); + if ($uptime < 3600) + { + # Don't run. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0706"}); + update_progress($anvil, 100, "log_0706"); + $anvil->nice_exit({exit_code => 1}); + } + + if (not $anvil->data->{switches}{'job-uuid'}) { # No job_uuid, so a manual call. return(0); diff --git a/tools/striker-prep-database b/tools/striker-prep-database index 173604ec..4d40321e 100755 --- a/tools/striker-prep-database +++ b/tools/striker-prep-database @@ -541,117 +541,8 @@ sub configure_firewall { my ($anvil) = @_; - # What zones are there? - my $in_zone = ""; - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --get-active-zones"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call, debug => 2, source => $THIS_FILE, line => __LINE__}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - output => $output, - return_code => $return_code, - }}); - - # If the return code was 252, firewalld isn't running. - if ($return_code eq "252") - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0669"}); - return(0); - } - - foreach my $line (split/\n/, $output) - { - if ($line =~ /^\S/) - { - $in_zone = $line; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { in_zone => $in_zone }}); - - $anvil->data->{firewall}{zone}{$in_zone}{active} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "firewall::zone::${in_zone}::active" => $anvil->data->{firewall}{zone}{$in_zone}{active}, - }}); - } - elsif ($line =~ /^\s+interfaces: (.*)$/) - { - my $interfaces = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { interfaces => $interfaces }}); - - $anvil->data->{firewall}{zone}{$in_zone}{interfaces} = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "firewall::zone::${in_zone}::interfaces" => $anvil->data->{firewall}{zone}{$in_zone}{interfaces}, - }}); - } - } - - my $reload = 0; - foreach my $zone (sort {$a cmp $b} keys %{$anvil->data->{firewall}{zone}}) - { - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --permanent --info-zone=".$zone; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call, debug => 2, source => $THIS_FILE, line => __LINE__}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - output => $output, - return_code => $return_code, - }}); - foreach my $line (split/\n/, $output) - { - if ($line =~ /^\s+services: (.*)$/) - { - my $services = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { services => $services }}); - - foreach my $service (split/ /, $services) - { - $anvil->data->{firewall}{zone}{$in_zone}{service}{$service} = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - "firewall::zone::${in_zone}::service::${service}" => $anvil->data->{firewall}{zone}{$in_zone}{service}{$service}, - }}); - } - } - } - - # Is postgres open? - if ((not exists $anvil->data->{firewall}{zone}{$in_zone}{service}{postgresql}) or (not $anvil->data->{firewall}{zone}{$in_zone}{service}{postgresql})) - { - ### TODO: Switch this to System->manage_firewall(). - # Enable it. - my $service = "postgresql"; - $reload = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - service => $service, - reload => $reload, - }}); - - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0667", variables => { - zone => $zone, - service => $service, - }}); - - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --zone=".$zone." --permanent --add-service=".$service; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call, debug => 2, source => $THIS_FILE, line => __LINE__}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - output => $output, - return_code => $return_code, - }}); - } - } - - # Reload the firewall? - if ($reload) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0139"}); - - my $shell_call = $anvil->data->{path}{exe}{'firewall-cmd'}." --reload"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - - my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call, debug => 2, source => $THIS_FILE, line => __LINE__}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - output => $output, - return_code => $return_code, - }}); - } + # All the firewall management is now in the method below. + $anvil->Network->manage_firewall(); return(0); }