#!/usr/bin/perl # # This daemon watches for network interface link change (unplugged or plugged in network cables). # # At this point, the only thing this does is call 'scan-network' when a change is detected. # # NOTE: This is designed to be minimal overhead, so there is no attempt to connect to the database. As such, # be mindful of what this daemon is used for. # use strict; use warnings; use Data::Dumper; use Text::Diff; use Anvil::Tools; my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0]; my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0]; if (($running_directory =~ /^\./) && ($ENV{PWD})) { $running_directory =~ s/^\./$ENV{PWD}/; } # Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete. $| = 1; my $anvil = Anvil::Tools->new(); # Read switches $anvil->Get->switches({list => [], man => $THIS_FILE}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }}); # Calculate my sum so that we can exit if it changes later. $anvil->Storage->record_md5sums; my $next_md5sum_check = time + 30; my $directory = "/sys/class/net"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { directory => $directory }}); # Now go into the main loop while(1) { ### NOTE: A lot of this logic comes from scan-network my $scan_time = time; my $trigger = 0; # Look for interfaces. local(*DIRECTORY); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0018", variables => { directory => $directory }}); opendir(DIRECTORY, $directory); while(my $file = readdir(DIRECTORY)) { next if $file eq "."; next if $file eq ".."; next if $file eq "lo"; my $full_path = $directory."/".$file; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { full_path => $full_path }}); if (-d $full_path) { # Pull out the data I want. Note that some of these don't exist with virtio-net interfaces. my $interface = $file; my $link_state = -e $full_path."/carrier" ? $anvil->Storage->read_file({file => $full_path."/carrier"}) : 0; $link_state =~ s/\n$//; my $mtu = -e $full_path."/mtu" ? $anvil->Storage->read_file({file => $full_path."/mtu"}) : 0; $mtu =~ s/\n$//; my $duplex = -e $full_path."/duplex" ? $anvil->Storage->read_file({file => $full_path."/duplex"}) : "unknown"; # full or half? $duplex =~ s/\n$//; my $operational = -e $full_path."/operstate" ? $anvil->Storage->read_file({file => $full_path."/operstate"}) : "unknown"; # up or down $operational =~ s/\n$//; my $modalias = -e $full_path."/device/modalias" ? $anvil->Storage->read_file({file => $full_path."/device/modalias"}) : "unknown"; $modalias =~ s/\n$//; my $speed = $link_state ? $anvil->Storage->read_file({file => $full_path."/speed"}) : 0; # Mbps (ie: 1000 = Gbps), gives a very high number for unplugged link $speed =~ s/\n$//; my $media = "unknown"; my $type = "interface"; my $driver = ""; my $tx_bytes = 0; # How many bytes transmitted my $rx_bytes = 0; # How many bytes received $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { interface => $interface, link_state => $link_state, mtu => $mtu, duplex => $duplex, operational => $operational, speed => $speed, modalias => $modalias, }}); # Get the MAC address my $mac_address = ""; my $shell_call = $anvil->data->{path}{exe}{ethtool}." -P ".$interface; $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}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code, }}); if ($output =~ /(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)$/) { $mac_address = lc($1); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { mac_address => $mac_address }}); } else { # Get it by reading the address file. if (-e $full_path."/bonding_slave/perm_hwaddr") { $mac_address = $anvil->Storage->read_file({file => $full_path."/bonding_slave/perm_hwaddr"}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { mac_address => $mac_address }}); } elsif (-e $full_path."/address") { $mac_address = $anvil->Storage->read_file({file => $full_path."/address"}); $mac_address =~ s/\n//; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { mac_address => $mac_address }}); } } # These are variables that will be needed if this is a bond interface. my $ip_address = ""; my $subnet_mask = ""; my $bond_mode = ""; my $primary_interface = ""; my $primary_reselect = ""; my $active_interface = ""; my $mii_polling_interval = ""; my $up_delay = ""; my $down_delay = ""; my $bond_parent = ""; # These are variables that will be needed if this is a bridge interface my $bridge_id = ""; my $bridge_stp_enabled = ""; # If this interface is already a bond slave, the real mac address will be in a # sub-directory. my $mac_bond_file = $directory."/".$file."/bonding_slave/perm_hwaddr"; if (-e $mac_bond_file) { # It's a slave. $mac_address = $anvil->Storage->read_file({file => $mac_bond_file}); $mac_address =~ s/\n$//; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { mac_address => $mac_address }}); } # Pick out our driver. if ($modalias =~ /^virtio:/) { $driver = "virtio"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { driver => $driver }}); } # If this is a virtual interface, set some fake values that don't actually exist on # the system for the sake of a cleaner display. if (($mac_address =~ /^52:54:00/) or ($driver eq "virtio")) { ### Set some fake values. # Speed is "as fast as possible", so we'll record 100 Gbps, but that is really kind of arbitrary. if ((not $speed) or ($speed eq "-1")) { $speed = 10000; } if ((not $duplex) or ($duplex eq "unknown")) { $duplex = "full"; } $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { speed => $speed, duplex => $duplex, }}); } # If the state is 'down', set the speed to '0'. if (not $link_state) { $speed = 0; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { speed => $speed }}); } # Is this a bond interface? if (-e "/proc/net/bonding/".$interface) { # Yup, we'll neet to dig into the bond proc files to get the proper slaved # interface MAC addresses. $type = "bond"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { type => $type }}); # Read the bond mode. $bond_mode = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/mode"}); $bond_mode =~ s/\s.*//; $bond_mode =~ s/\n$//; $primary_interface = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/primary"}); $primary_interface =~ s/\n$//; $primary_reselect = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/primary_reselect"}); $primary_reselect =~ s/\s.*//; $primary_reselect =~ s/\n$//; $active_interface = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/active_slave"}); $active_interface =~ s/\n$//; $mii_polling_interval = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/miimon"}); $mii_polling_interval =~ s/\n$//; $up_delay = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/updelay"}); $up_delay =~ s/\n$//; $down_delay = $anvil->Storage->read_file({file => "/sys/devices/virtual/net/".$interface."/bonding/downdelay"}); $down_delay =~ s/\n$//; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { active_interface => $active_interface, bond_mode => $bond_mode, mii_polling_interval => $mii_polling_interval, primary_reselect => $primary_reselect, primary_interface => $primary_interface, type => $type, }}); } elsif ((-e $full_path."/master") && ($interface !~ /^vnet/)) { # We're in a bond. my $target = readlink($full_path."/master"); $bond_parent = ($target =~ /^.*\/(.*)$/)[0]; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { target => $target, bond_parent => $bond_parent, }}); } elsif (-d $full_path."/bridge") { # It's a bridge $type = "bridge"; $bridge_id = $anvil->Storage->read_file({debug => 3, file => $full_path."/bridge/bridge_id"}); $bridge_id =~ s/\n$//; $bridge_stp_enabled = $anvil->Storage->read_file({debug => 3, file => $full_path."/bridge/stp_state"}); $bridge_stp_enabled =~ s/\n$//; $speed = 0; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { type => $type, bridge_id => $bridge_id, bridge_stp_enabled => $bridge_stp_enabled, }}); if ($bridge_stp_enabled eq "0") { $bridge_stp_enabled = "disabled"; } elsif ($bridge_stp_enabled eq "1") { $bridge_stp_enabled = "enabled_kernel"; } elsif ($bridge_stp_enabled eq "2") { $bridge_stp_enabled = "enabled_userland"; } $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bridge_stp_enabled => $bridge_stp_enabled }}); } # If this is a 'vnet' device, set 'operational' to up if ($interface =~ /^vnet/) { ### TODO: We can't assume this, we need to detect virsh net up/down $operational = "up"; $media = "virtual"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { operational => $operational, media => $media, }}); } $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { active_interface => $active_interface, bond_parent => $bond_parent, bond_mode => $bond_mode, bridge_id => $bridge_id, bridge_stp_enabled => $bridge_stp_enabled, down_delay => $down_delay, duplex => $duplex, interface => $interface, mac_address => $mac_address, mii_polling_interval => $mii_polling_interval, mtu => $mtu, operational => $operational, primary_reselect => $primary_reselect, primary_interface => $primary_interface, speed => $speed, subnet_mask => $subnet_mask, type => $type, up_delay => $up_delay, }}); # Find the media, if possible. (my $ethtool, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{ethtool}." ".$interface}); foreach my $line (split/\n/, $ethtool) { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }}); if ($line =~ /Supported ports: \[ (.*?) \]/i) { $media = lc($1); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { media => $media }}); # This can be 'tp mii', which breaks json. if ($media =~ /\t/) { $media =~ s/\t/,/g; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { media => $media }}); } last; } } # Trigger? if (not exists $anvil->data->{last_scan}{$interface}) { print "The interface: [".$interface."] has been found. We will now monitor it for changes.\n"; $trigger = 1; } else { # Now look for differences. if ($anvil->data->{last_scan}{$interface}{active_interface} ne $active_interface) { print "The ".$type.": [".$interface."] has a different active interface: [".$anvil->data->{last_scan}{$interface}{active_interface}."] -> [".$active_interface."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{bond_mode} ne $bond_mode) { print "The ".$type.": [".$interface."] mode has changed from: [".$anvil->data->{last_scan}{$interface}{bond_mode}."] -> [".$bond_mode."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{bond_parent} ne $bond_parent) { print "The ".$type.": [".$interface."] bond parent has changed from: [".$anvil->data->{last_scan}{$interface}{bond_parent}."] -> [".$bond_parent."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{bridge_id} ne $bridge_id) { print "The ".$type.": [".$interface."] bridge ID has changed from: [".$anvil->data->{last_scan}{$interface}{bridge_id}."] -> [".$bridge_id."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{bridge_stp_enabled} ne $bridge_stp_enabled) { print "The ".$type.": [".$interface."] spanning tree protocol (STP) setting has changed from: [".$anvil->data->{last_scan}{$interface}{bridge_stp_enabled}."] -> [".$bridge_stp_enabled."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{down_delay} ne $down_delay) { print "The ".$type.": [".$interface."] down delay has changed from: [".$anvil->data->{last_scan}{$interface}{bridge_stp_enabled}."ms] -> [".$bridge_stp_enabled."ms].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{up_delay} ne $up_delay) { print "The ".$type.": [".$interface."] up delay has changed from: [".$anvil->data->{last_scan}{$interface}{up_delay}."ms] -> [".$up_delay."ms].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{duplex} ne $duplex) { print "The ".$type.": [".$interface."] duplex has changed from: [".$anvil->data->{last_scan}{$interface}{duplex}."] -> [".$duplex."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{ip_address} ne $ip_address) { print "The ".$type.": [".$interface."] ip address has changed from: [".$anvil->data->{last_scan}{$interface}{ip_address}."] -> [".$ip_address."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{subnet_mask} ne $subnet_mask) { print "The ".$type.": [".$interface."] subnet_mask has changed from: [".$anvil->data->{last_scan}{$interface}{subnet_mask}."] -> [".$subnet_mask."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{link_state} ne $link_state) { print "The ".$type.": [".$interface."] link status has changed from: [".$anvil->data->{last_scan}{$interface}{link_state}."] -> [".$link_state."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{mac_address} ne $mac_address) { print "The ".$type.": [".$interface."] MAC address has changed from: [".$anvil->data->{last_scan}{$interface}{mac_address}."] -> [".$mac_address."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{media} ne $media) { print "The ".$type.": [".$interface."] media has changed from: [".$anvil->data->{last_scan}{$interface}{media}."] -> [".$media."]. (Excuse me, how?!)\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{mii_polling_interval} ne $mii_polling_interval) { print "The ".$type.": [".$interface."] media independent interface (mii) polling interval has changed from: [".$anvil->data->{last_scan}{$interface}{mii_polling_interval}."ms] -> [".$mii_polling_interval."ms].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{mtu} ne $mtu) { print "The ".$type.": [".$interface."] maximum transmission unit (mtu) has changed from: [".$anvil->data->{last_scan}{$interface}{mtu}." bytes] -> [".$mtu." bytes].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{operational} ne $operational) { print "The ".$type.": [".$interface."] operational status has changed from: [".$anvil->data->{last_scan}{$interface}{operational}."] -> [".$operational."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{primary_reselect} ne $primary_reselect) { print "The ".$type.": [".$interface."] primary reselect policy has changed from: [".$anvil->data->{last_scan}{$interface}{primary_reselect}."] -> [".$primary_reselect."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{primary_interface} ne $primary_interface) { print "The ".$type.": [".$interface."] primary interface has changed from: [".$anvil->data->{last_scan}{$interface}{primary_interface}."] -> [".$primary_interface."].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{speed} ne $speed) { print "The ".$type.": [".$interface."] speed has changed from: [".$anvil->data->{last_scan}{$interface}{speed}." Mbps] -> [".$speed." Mbps].\n"; $trigger = 1; } if ($anvil->data->{last_scan}{$interface}{type} ne $type) { print "The device: [".$interface."] type has changed from: [".$anvil->data->{last_scan}{$interface}{type}."] -> [".$type."].\n"; $trigger = 1; } } # We'll use this to determine when an interface has disappeared. $anvil->data->{last_scan}{$interface}{seen} = $scan_time; # Store new information we found. $anvil->data->{last_scan}{$interface}{active_interface} = $active_interface; $anvil->data->{last_scan}{$interface}{bond_mode} = $bond_mode; $anvil->data->{last_scan}{$interface}{bond_parent} = $bond_parent; $anvil->data->{last_scan}{$interface}{bridge_id} = $bridge_id; $anvil->data->{last_scan}{$interface}{bridge_stp_enabled} = $bridge_stp_enabled; $anvil->data->{last_scan}{$interface}{down_delay} = $down_delay; $anvil->data->{last_scan}{$interface}{duplex} = $duplex; $anvil->data->{last_scan}{$interface}{ip_address} = $ip_address; $anvil->data->{last_scan}{$interface}{link_state} = $link_state; $anvil->data->{last_scan}{$interface}{mac_address} = $mac_address; $anvil->data->{last_scan}{$interface}{media} = $media; $anvil->data->{last_scan}{$interface}{mii_polling_interval} = $mii_polling_interval; $anvil->data->{last_scan}{$interface}{mtu} = $mtu; $anvil->data->{last_scan}{$interface}{operational} = $operational; $anvil->data->{last_scan}{$interface}{primary_reselect} = $primary_reselect; $anvil->data->{last_scan}{$interface}{primary_interface} = $primary_interface; $anvil->data->{last_scan}{$interface}{speed} = $speed; $anvil->data->{last_scan}{$interface}{subnet_mask} = $subnet_mask; $anvil->data->{last_scan}{$interface}{type} = $type; $anvil->data->{last_scan}{$interface}{up_delay} = $up_delay; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "last_scan::${interface}::seen" => $anvil->data->{last_scan}{$interface}{seen}, "last_scan::${interface}::active_interface" => $anvil->data->{last_scan}{$interface}{active_interface}, "last_scan::${interface}::bond_mode" => $anvil->data->{last_scan}{$interface}{bond_mode}, "last_scan::${interface}::bond_parent" => $anvil->data->{last_scan}{$interface}{bond_parent}, "last_scan::${interface}::bridge_id" => $anvil->data->{last_scan}{$interface}{bridge_id}, "last_scan::${interface}::bridge_stp_enabled" => $anvil->data->{last_scan}{$interface}{bridge_stp_enabled}, "last_scan::${interface}::down_delay" => $anvil->data->{last_scan}{$interface}{down_delay}, "last_scan::${interface}::duplex" => $anvil->data->{last_scan}{$interface}{duplex}, "last_scan::${interface}::ip_address" => $anvil->data->{last_scan}{$interface}{ip_address}, "last_scan::${interface}::link_state" => $anvil->data->{last_scan}{$interface}{link_state}, "last_scan::${interface}::mac_address" => $anvil->data->{last_scan}{$interface}{mac_address}, "last_scan::${interface}::media" => $anvil->data->{last_scan}{$interface}{media}, "last_scan::${interface}::mii_polling_interval" => $anvil->data->{last_scan}{$interface}{mii_polling_interval}, "last_scan::${interface}::mtu" => $anvil->data->{last_scan}{$interface}{mtu}, "last_scan::${interface}::operational" => $anvil->data->{last_scan}{$interface}{operational}, "last_scan::${interface}::primary_reselect" => $anvil->data->{last_scan}{$interface}{primary_reselect}, "last_scan::${interface}::primary_interface" => $anvil->data->{last_scan}{$interface}{primary_interface}, "last_scan::${interface}::speed" => $anvil->data->{last_scan}{$interface}{speed}, "last_scan::${interface}::subnet_mask" => $anvil->data->{last_scan}{$interface}{subnet_mask}, "last_scan::${interface}::type" => $anvil->data->{last_scan}{$interface}{type}, "last_scan::${interface}::up_delay" => $anvil->data->{last_scan}{$interface}{up_delay}, }}); } } closedir(DIRECTORY); # Now look for interfaces that disappeared. foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{last_scan}}) { next if $anvil->data->{last_scan}{$interface}{seen} == $scan_time; print "The device: [".$interface."] appears to have disappeared!\n"; delete $anvil->data->{last_scan}{$interface}; $trigger = 1; } # Trigger? if ($trigger) { my $shell_call = $anvil->data->{path}{directories}{scan_agents}."/scan-network/scan-network".$anvil->Log->switches; print "Triggering the call to run: [".$shell_call."]\n"; $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}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code, }}); } if (time > $next_md5sum_check) { $next_md5sum_check = time + 30; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { next_md5sum_check => $next_md5sum_check }}); if ($anvil->Storage->check_md5sums) { # NOTE: We exit with '0' to prevent systemctl from showing a scary red message. $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "message_0014"}); $anvil->nice_exit({exit_code => 0}); } } sleep 2; }