From 2e4133507afbb6ca2ebc74c4ec773a9c75dcdd65 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 21 Mar 2024 10:34:54 -0400 Subject: [PATCH] Add Network.pm --- scripts/ZoneMinder/lib/ZoneMinder/Network.pm | 215 +++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 scripts/ZoneMinder/lib/ZoneMinder/Network.pm diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Network.pm b/scripts/ZoneMinder/lib/ZoneMinder/Network.pm new file mode 100644 index 000000000..61f3e32c8 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Network.pm @@ -0,0 +1,215 @@ +package ZoneMinder::Network; + +use 5.006; +use strict; +use warnings; + +require Exporter; +require ZoneMinder::Base; + +our @ISA = qw(Exporter ZoneMinder::Base); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +# This allows declaration use ZoneMinder ':all'; +# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK +# will save memory. +our %EXPORT_TAGS = ( + functions => [ qw( + getInterfaces + getNetworks + getSubnets + getArpScan + getVendor + ) ] + ); +push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); + +our @EXPORT = qw(); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); + +use POSIX; +use Data::Dumper; + +our %MacVendors = (); + +sub getInterfaces { + my @interfaces = (); + my $output = `ip link`; + foreach my $line ( split(/\n/, $output) ) { + if ( $line =~ /^\d+: ([[:alnum:]]+):/ ) { + push @interfaces, $1; + + } else { + Debug("No match for $line"); + } + } # end foreach line + return @interfaces; +} # end sub getInterfaces + +sub getNetworks { + # Skip lo, not interesting in localhost + my %interfaces = map { $_ ne 'lo' ? ($_ => []) : () } getInterfaces(); + + # Add routes + my $output = `ip route`; + foreach my $line ( split(/\n/, $output ) ) { + if ( $line =~ /^default via [.\d]+ dev (\w+)/ ) { + $interfaces{default} = $1; + } elsif ( ($line =~ /^([.\d]+\/\d+) dev (\w+)/) ) { + if ($1 ne '169.254.0.0/16') { + $interfaces{$2} = [] if !$interfaces{$2}; + push @{$interfaces{$2}}, $1; + } + } else { + Debug("Didn't match line $line in getNetworks"); + } + } # end foreach line of output + return %interfaces; +} + +sub getSubnets { +} + +# Returns a hash with ip as key, mac as value +sub getArpScan { + my $network = shift; + my $device = shift; + my %hosts = (); + Debug("/usr/sbin/arp-scan $network -I $device 2>&1"); + my $output = `/usr/sbin/arp-scan $network -I $device 2>&1`; + if ($output) { + Debug($output); + foreach my $line (split(/\n/, $output)) { + if ($line =~ /^(\d+\.\d+\.\d+\.\d+)\s+([0-9a-f:]+)\s+(.*)$/) { + $hosts{$1} = {ip=>$1, mac=>$2, vendor=>$3}; + } else { + Debug("Didn't match preg $line"); + } + } # end foreach line + } else { + Error("No output from /usr/sbin/arp-scan $network 2>&1"); + } + return %hosts; +} # end sub getArpScan + +# Enhanced OUI get Vendor. Uses our own database and will cache. +sub getVendor { + my $mac = shift; + my $vendor = ''; + + + if (!%MacVendors) { + my $file = '/usr/share/arp-scan/ieee-oui.txt'; + if ( -e $file) { + open my $info, $file or die "Could not open $file: $!"; + while ( my $line = <$info>) { + next if $line =~ /^#/; + $line =~ /^([A-Fa-f0-9]{6})\s+(.*)$/; + $MacVendors{lc $1} = { vendor=>$2 }; + } + close $info; + } + if (-e $Config{ZM_PATH_DATA}.'/MacVendors.json') { + my $json = ZoneMinder::General::jsonLoad($Config{ZM_PATH_DATA}.'/MacVendors.json'); + if ($json) { + Debug(Data::Dumper::Dumper($json)); + @MacVendors{keys %{$json}} = values %{$json}; + } else { + Warning("No json for " . $Config{ZM_PATH_DATA}.'/MacVendors.json'); + } + } else { + Warning('MacVendors does not exists at '.$Config{ZM_PATH_DATA}.'/MacVendors.json'); + } + } + my $oui = lc $mac; + $oui =~ s/[^0-9a-f]//g; + $oui = substr($oui, 0, 6); + + if (%MacVendors and $MacVendors{$oui}) { + return $MacVendors{$oui}; + } + + eval { + require LWP::UserAgent; + my $ua = LWP::UserAgent->new(); + my $url = 'https://api.macvendors.com/' . $mac; + my $response = $ua->get($url); + if ($response->is_success) { + $vendor = $response->content; + } else { + print $response->status_line; + } + }; + Error("Error in eval: $@") if $@; + if ($vendor) { + $MacVendors{$oui} = { vendor=>$vendor }; + } + + return $MacVendors{$oui}; +} # end sub getVendor + + +1; +__END__ + +=head1 NAME + +ZoneMinder::Network - Network Utility Functions for ZoneMinder + +=head1 SYNOPSIS + +use ZoneMinder::Network; + +=head1 DESCRIPTION + +This module contains functions used by the rest +of the ZoneMinder scripts to get network information + +=head2 EXPORT + + functions => [ qw( + getInterfaces + getNetworks + getSubnets + getArpScan + ) ] + + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2022 ZoneMinder Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=cut