Merge branch 'master' of ssh://git.code.sf.net/p/netdisco/netdisco-ng
This commit is contained in:
		| @@ -1,13 +1,35 @@ | ||||
| 2.017001 - | ||||
| 2.018001 -  | ||||
|  | ||||
|   [BUG FIXES] | ||||
|  | ||||
|   * Update Print media CSS to handle new UI components | ||||
|  | ||||
| 2.018000 - 2013-10-08 | ||||
|  | ||||
|   [NEW FEATURES] | ||||
|  | ||||
|   * Add VLAN Inventory Report | ||||
|   * Add Wireless SSID Inventory Report | ||||
|   * Add Device Inventory by Location Report | ||||
|   * Node DNS names resolved in their own job - see nodenames_no and nodenames_only | ||||
|  | ||||
|   [ENHANCEMENTS] | ||||
|  | ||||
|   * Respect ignore_interfaces and i_ignore when detecting wrapped device uptime | ||||
|   * Try NodeIp OUI company name search if no node results found | ||||
|   * Format About page numbers | ||||
|  | ||||
|   [BUG FIXES] | ||||
|  | ||||
|   * Update NodeWireless entries which match both MAC and SSID found, only | ||||
|   * Fix SSL-proxy behaviour by using only path+query in links (W. Gould) | ||||
|   * Avoid macsuck generated SQL bug when cleaning NULL VLAN (W. Gould) | ||||
|   * During macsuck get VLAN from Q-BRIDGE if available (jeneric) | ||||
|   * OK to include device ports when doing arpnip (jeneric) | ||||
|   * Correct bulkwalk_off logic | ||||
|   * Silence warnings when ports don't support i_lastchange | ||||
|   * Correct *_only and *_no setting logic | ||||
|   * Correct the instructions for runing dev instance of web and daemon | ||||
|  | ||||
| 2.017000 - 2013-09-23 | ||||
|  | ||||
|   | ||||
| @@ -146,9 +146,12 @@ lib/App/Netdisco/Web/Plugin/Device/Ports.pm | ||||
| lib/App/Netdisco/Web/Plugin/Inventory.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm | ||||
| lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm | ||||
| lib/App/Netdisco/Web/Plugin/Search/Device.pm | ||||
| lib/App/Netdisco/Web/Plugin/Search/Node.pm | ||||
| lib/App/Netdisco/Web/Plugin/Search/Port.pm | ||||
| @@ -235,12 +238,18 @@ share/views/ajax/report/apchanneldist.tt | ||||
| share/views/ajax/report/apchanneldist_csv.tt | ||||
| share/views/ajax/report/apradiochannelpower.tt | ||||
| share/views/ajax/report/apradiochannelpower_csv.tt | ||||
| share/views/ajax/report/devicebylocation.tt | ||||
| share/views/ajax/report/devicebylocation_csv.tt | ||||
| share/views/ajax/report/duplexmismatch.tt | ||||
| share/views/ajax/report/duplexmismatch_csv.tt | ||||
| share/views/ajax/report/halfduplex.tt | ||||
| share/views/ajax/report/halfduplex_csv.tt | ||||
| share/views/ajax/report/portutilization.tt | ||||
| share/views/ajax/report/portutilization_csv.tt | ||||
| share/views/ajax/report/ssidinventory.tt | ||||
| share/views/ajax/report/ssidinventory_csv.tt | ||||
| share/views/ajax/report/vlaninventory.tt | ||||
| share/views/ajax/report/vlaninventory_csv.tt | ||||
| share/views/ajax/search/device.tt | ||||
| share/views/ajax/search/device_csv.tt | ||||
| share/views/ajax/search/node_by_ip.tt | ||||
|   | ||||
| @@ -25,11 +25,11 @@ requires: | ||||
|   DBD::Pg: 0 | ||||
|   DBD::SQLite: 1.37 | ||||
|   DBIx::Class: 0.0821 | ||||
|   DBIx::Class::Helpers: 2.016006 | ||||
|   DBIx::Class::Helpers: 2.018004 | ||||
|   Daemon::Control: 0.001 | ||||
|   Dancer: 1.3112 | ||||
|   Dancer::Plugin::Auth::Extensible: 0.2 | ||||
|   Dancer::Plugin::DBIC: 0.1802 | ||||
|   Dancer::Plugin::DBIC: 0.1803 | ||||
|   File::ShareDir: 1.03 | ||||
|   HTML::Parser: 3.7 | ||||
|   HTTP::Tiny: 0.029 | ||||
| @@ -52,6 +52,7 @@ requires: | ||||
|   Starman: 0.4008 | ||||
|   Template: 2.24 | ||||
|   Template::Plugin::CSV: 0.04 | ||||
|   Template::Plugin::Number::Format: 1.02 | ||||
|   URL::Encode: 0.01 | ||||
|   YAML: 0.84 | ||||
|   YAML::XS: 0.41 | ||||
| @@ -64,4 +65,4 @@ resources: | ||||
|   homepage: http://netdisco.org/ | ||||
|   license: http://opensource.org/licenses/bsd-license.php | ||||
|   repository: git://git.code.sf.net/p/netdisco/netdisco-ng | ||||
| version: 2.017000 | ||||
| version: 2.018000 | ||||
|   | ||||
| @@ -10,10 +10,10 @@ requires 'App::local::lib::helper' => 0.07; | ||||
| requires 'DBD::Pg' => 0; | ||||
| requires 'DBD::SQLite' => 1.37; | ||||
| requires 'DBIx::Class' => 0.08210; | ||||
| requires 'DBIx::Class::Helpers' => 2.016006; | ||||
| requires 'DBIx::Class::Helpers' => 2.018004; | ||||
| requires 'Daemon::Control' => 0.001000; | ||||
| requires 'Dancer' => 1.3112; | ||||
| requires 'Dancer::Plugin::DBIC' => 0.1802; | ||||
| requires 'Dancer::Plugin::DBIC' => 0.1803; | ||||
| requires 'Dancer::Plugin::Auth::Extensible' => 0.20; | ||||
| requires 'File::ShareDir' => 1.03; | ||||
| requires 'HTML::Parser' => 3.70; | ||||
|   | ||||
| @@ -7,7 +7,7 @@ use 5.010_000; | ||||
| use File::ShareDir 'dist_dir'; | ||||
| use Path::Class; | ||||
|  | ||||
| our $VERSION = '2.017000'; | ||||
| our $VERSION = '2.018000'; | ||||
|  | ||||
| BEGIN { | ||||
|   if (not ($ENV{DANCER_APPDIR} || '') | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package App::Netdisco::Core::Arpnip; | ||||
| use Dancer qw/:syntax :script/; | ||||
| use Dancer::Plugin::DBIC 'schema'; | ||||
|  | ||||
| use App::Netdisco::Util::PortMAC 'get_port_macs'; | ||||
| use App::Netdisco::Util::SanityCheck 'check_mac'; | ||||
| use App::Netdisco::Util::DNS ':all'; | ||||
| use NetAddr::IP::Lite ':lower'; | ||||
| @@ -11,7 +10,7 @@ use Time::HiRes 'gettimeofday'; | ||||
|  | ||||
| use base 'Exporter'; | ||||
| our @EXPORT = (); | ||||
| our @EXPORT_OK = qw/ do_arpnip store_arp /; | ||||
| our @EXPORT_OK = qw/ do_arpnip store_arp resolve_node_names /; | ||||
| our %EXPORT_TAGS = (all => \@EXPORT_OK); | ||||
|  | ||||
| =head1 NAME | ||||
| @@ -44,12 +43,10 @@ sub do_arpnip { | ||||
|       return; | ||||
|   } | ||||
|  | ||||
|   my $port_macs = get_port_macs($device); | ||||
|  | ||||
|   # get v4 arp table | ||||
|   my @v4 = _get_arps($device, $port_macs, $snmp->at_paddr, $snmp->at_netaddr); | ||||
|   my @v4 = _get_arps($device, $snmp->at_paddr, $snmp->at_netaddr); | ||||
|   # get v6 neighbor cache | ||||
|   my @v6 = _get_arps($device, $port_macs, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr); | ||||
|   my @v6 = _get_arps($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr); | ||||
|  | ||||
|   # get directly connected networks | ||||
|   my @subnets = _gather_subnets($device, $snmp); | ||||
| @@ -78,23 +75,23 @@ sub do_arpnip { | ||||
|  | ||||
| # get an arp table (v4 or v6) | ||||
| sub _get_arps { | ||||
|   my ($device, $port_macs, $paddr, $netaddr) = @_; | ||||
|   my ($device, $paddr, $netaddr) = @_; | ||||
|   my @arps = (); | ||||
|  | ||||
|   while (my ($arp, $node) = each %$paddr) { | ||||
|       my $ip = $netaddr->{$arp}; | ||||
|       next unless defined $ip; | ||||
|       next unless check_mac($device, $node, $port_macs); | ||||
|       push @arps, [$node, $ip, hostname_from_ip($ip)]; | ||||
|       next unless check_mac($device, $node); | ||||
|       push @arps, [$node, $ip]; | ||||
|   } | ||||
|  | ||||
|   return @arps; | ||||
| } | ||||
|  | ||||
| =head2 store_arp( $mac, $ip, $name, $now? ) | ||||
| =head2 store_arp( $mac, $ip, $now? ) | ||||
|  | ||||
| Stores a new entry to the C<node_ip> table with the given MAC, IP (v4 or v6) | ||||
| and DNS host name. | ||||
| Stores a new entry to the C<node_ip> table with the given MAC, and IP (v4 or | ||||
| v6). | ||||
|  | ||||
| Will mark old entries for this IP as no longer C<active>. | ||||
|  | ||||
| @@ -104,7 +101,7 @@ C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used. | ||||
| =cut | ||||
|  | ||||
| sub store_arp { | ||||
|   my ($mac, $ip, $name, $now) = @_; | ||||
|   my ($mac, $ip, $now) = @_; | ||||
|   $now ||= 'now()'; | ||||
|  | ||||
|   schema('netdisco')->txn_do(sub { | ||||
| @@ -122,7 +119,6 @@ sub store_arp { | ||||
|       ->search({'me.mac' => $mac, 'me.ip' => $ip}) | ||||
|       ->update_or_create( | ||||
|       { | ||||
|         dns => $name, | ||||
|         active => \'true', | ||||
|         time_last => \$now, | ||||
|       }, | ||||
| @@ -175,4 +171,39 @@ sub _store_subnet { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| =head2 resolve_node_names( $device ) | ||||
|  | ||||
| Given a Device database object, resolve Node IP (ARP) entries belonging to | ||||
| this device into DNS names, and store them in the C<node_ip> database table. | ||||
|  | ||||
| This action is usually queued following C<do_arpip> so that it may run | ||||
| asynchronously, and/or on another daemon worker node. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub resolve_node_names { | ||||
|   my ($device) = @_; | ||||
|  | ||||
|   schema('netdisco')->txn_do(sub { | ||||
|     my $nodeips = schema('netdisco') | ||||
|       ->resultset('NodeIp')->search( | ||||
|         { | ||||
|           -and => [ | ||||
|             -bool => 'me.active', | ||||
|             -bool => 'nodes.active', | ||||
|           ], | ||||
|           'nodes.switch' => $device->ip, | ||||
|         }, | ||||
|         { | ||||
|           join => 'nodes', | ||||
|           for => 'update', | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|     while (my $nodeip = $nodeips->next) { | ||||
|       $nodeip->update({dns => hostname_from_ip($nodeip->ip)}); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -94,6 +94,10 @@ sub do_macsuck { | ||||
|             $device->ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} }; | ||||
|  | ||||
|           foreach my $mac (keys %{ $fwtable->{$vlan}->{$port} }) { | ||||
|               # get VLAN from Q-BRIDGE if available | ||||
|               $vlan = $fwtable->{$vlan}->{$port}->{$mac} | ||||
|                 if $vlan == 0; | ||||
|  | ||||
|               # remove vlan 0 entry for this MAC addr | ||||
|               delete $fwtable->{0}->{$_}->{$mac} | ||||
|                 for keys %{ $fwtable->{0} }; | ||||
| @@ -130,53 +134,48 @@ sub store_node { | ||||
|     my $nodes = schema('netdisco')->resultset('Node'); | ||||
|  | ||||
|     # TODO: probably needs changing if we're to support VTP domains | ||||
|     my $old = $nodes->search( | ||||
|       { | ||||
|         mac => $mac, | ||||
|         vlan => $vlan, | ||||
|         -bool => 'active', | ||||
|         -not => { | ||||
|           switch => $ip, | ||||
|           port => $port, | ||||
|         }, | ||||
|       }); | ||||
|     my $old = $nodes->search({ | ||||
|       mac => $mac, | ||||
|       vlan => $vlan, | ||||
|       -bool => 'active', | ||||
|       -not => { | ||||
|         switch => $ip, | ||||
|         port => $port, | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
|     # lock rows, | ||||
|     # and get the count so we know whether to set time_recent | ||||
|     my $old_count = scalar $old->search(undef, | ||||
|       { | ||||
|         columns => [qw/switch vlan port mac/], | ||||
|         order_by => [qw/switch vlan port mac/], | ||||
|         for => 'update', | ||||
|       })->all; | ||||
|  | ||||
|     $old->update({ active => \'false' }); | ||||
|  | ||||
|     my $new = $nodes->search( | ||||
|       { | ||||
|         'me.switch' => $ip, | ||||
|         'me.port' => $port, | ||||
|         'me.mac' => $mac, | ||||
|       }, | ||||
|       { | ||||
|         order_by => [qw/switch vlan port mac/], | ||||
|         for => 'update', | ||||
|       }); | ||||
|  | ||||
|     # lock rows | ||||
|     $new->search({vlan => [$vlan, 0, undef]})->first; | ||||
|  | ||||
|     # upgrade old schema | ||||
|     $new->search({vlan => [0, undef]})->delete(); | ||||
|     my $new = $nodes->search({ | ||||
|       'me.switch' => $ip, | ||||
|       'me.port' => $port, | ||||
|       'me.mac' => $mac, | ||||
|     }); | ||||
|  | ||||
|     # new data | ||||
|     $new->update_or_create({ | ||||
|       vlan => $vlan, | ||||
|       active => \'true', | ||||
|       oui => substr($mac,0,8), | ||||
|       time_last => \$now, | ||||
|       ($old_count ? (time_recent => \$now) : ()), | ||||
|     }); | ||||
|     $new->update_or_create( | ||||
|       { | ||||
|         vlan => $vlan, | ||||
|         active => \'true', | ||||
|         oui => substr($mac,0,8), | ||||
|         time_last => \$now, | ||||
|         ($old_count ? (time_recent => \$now) : ()), | ||||
|       }, | ||||
|       { for => 'update' } | ||||
|     ); | ||||
|  | ||||
|     # upgrade old schema | ||||
|     # an entry for this MAC existed in old schema with vlan => null | ||||
|     # which now has either vlan number or 0 | ||||
|     $new->search({vlan => undef})->delete({only_nodes => 1}); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @@ -387,7 +386,7 @@ sub _walk_fwtable { | ||||
|           next unless setting('macsuck_bleed'); | ||||
|       } | ||||
|  | ||||
|       ++$cache->{$port}->{$mac}; | ||||
|       $cache->{$port}->{$mac} = ($fw_vlan->{$idx} || '0'); | ||||
|   } | ||||
|  | ||||
|   return $cache; | ||||
|   | ||||
| @@ -100,6 +100,10 @@ sub delete { | ||||
|       # avoid letting DBIC delete nodes | ||||
|       return 0E0; | ||||
|   } | ||||
|   elsif (exists $opts->{only_nodes} and $opts->{only_nodes}) { | ||||
|       # now let DBIC do its thing | ||||
|       return $self->next::method(); | ||||
|   } | ||||
|   elsif (exists $opts->{keep_nodes} and $opts->{keep_nodes}) { | ||||
|       # avoid letting DBIC delete nodes | ||||
|       return 0E0; | ||||
|   | ||||
| @@ -22,7 +22,7 @@ sub capacity_for { | ||||
|   debug "checking local capacity for action $action"; | ||||
|  | ||||
|   my $action_map = { | ||||
|     Poller => [qw/discoverall discover arpwalk arpnip macwalk macsuck/], | ||||
|     Poller => [qw/discoverall discover arpwalk arpnip nodenames macwalk macsuck/], | ||||
|     Interactive => [qw/location contact portcontrol portname vlan power/], | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ my $fqdn = hostfqdn || 'localhost'; | ||||
|  | ||||
| my $role_map = { | ||||
|   (map {$_ => 'Poller'} | ||||
|       qw/discoverall discover arpwalk arpnip macwalk macsuck/), | ||||
|       qw/discoverall discover arpwalk arpnip nodenames macwalk macsuck/), | ||||
|   (map {$_ => 'Interactive'} | ||||
|       qw/location contact portcontrol portname vlan power/) | ||||
| }; | ||||
|   | ||||
| @@ -1,9 +1,17 @@ | ||||
| package App::Netdisco::Daemon::Worker::Poller::Arpnip; | ||||
|  | ||||
| use Dancer::Plugin::DBIC 'schema'; | ||||
|  | ||||
| use App::Netdisco::Core::Arpnip 'do_arpnip'; | ||||
| use App::Netdisco::Util::Device 'is_arpnipable'; | ||||
| use App::Netdisco::Util::Device qw/get_device is_arpnipable can_nodenames/; | ||||
|  | ||||
| use App::Netdisco::Core::Arpnip 'resolve_node_names'; | ||||
| use App::Netdisco::Daemon::Util ':all'; | ||||
|  | ||||
| use NetAddr::IP::Lite ':lower'; | ||||
|  | ||||
| use Role::Tiny; | ||||
| use Class::Method::Modifiers; | ||||
| use namespace::clean; | ||||
|  | ||||
| with 'App::Netdisco::Daemon::Worker::Poller::Common'; | ||||
| @@ -15,4 +23,43 @@ sub arpnip_layer { 3 } | ||||
| sub arpwalk { (shift)->_walk_body('arpnip', @_) } | ||||
| sub arpnip  { (shift)->_single_body('arpnip', @_) } | ||||
|  | ||||
| after 'arpnip' => sub { | ||||
|   my ($self, $job) = @_; | ||||
|  | ||||
|   my $host = NetAddr::IP::Lite->new($job->device); | ||||
|   my $device = get_device($host->addr); | ||||
|   my $jobqueue = schema('netdisco')->resultset('Admin'); | ||||
|  | ||||
|   schema('netdisco')->txn_do(sub { | ||||
|     $jobqueue->create({ | ||||
|       device => $device->ip, | ||||
|       action => 'nodenames', | ||||
|       status => 'queued', | ||||
|       username => $job->username, | ||||
|       userip => $job->userip, | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| # run a nodenames job for one device | ||||
| sub nodenames { | ||||
|   my ($self, $job) = @_; | ||||
|  | ||||
|   my $host = NetAddr::IP::Lite->new($job->device); | ||||
|   my $device = get_device($host->addr); | ||||
|   my $jobqueue = schema('netdisco')->resultset('Admin'); | ||||
|  | ||||
|   if ($device->ip eq '0.0.0.0') { | ||||
|       return job_error("nodenames failed: no device param (need -d ?)"); | ||||
|   } | ||||
|  | ||||
|   unless (can_nodenames($device->ip)) { | ||||
|       return job_defer("nodenames deferred: cannot run for $host"); | ||||
|   } | ||||
|  | ||||
|   resolve_node_names($device); | ||||
|  | ||||
|   return job_done("Ended nodenames for ". $host->addr); | ||||
| } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -444,6 +444,22 @@ Value: Number. Default: 0. | ||||
| Sets the minimum amount of time in seconds which must elapse between any two | ||||
| arpnip jobs for a device. | ||||
|  | ||||
| =head3 C<nodenames_no> | ||||
|  | ||||
| Value: List of Network Identifiers or Device Properties. Default: Empty List. | ||||
|  | ||||
| Devices in the list will not have their Node IPs resolved to names using the | ||||
| DNS. You can include hostnames, IP addresses and subnets (IPv4 or IPv6) in the | ||||
| list. | ||||
|  | ||||
| =head3 C<nodenames_only> | ||||
|  | ||||
| Value: List of Network Identifiers or Device Properties. Default: Empty List. | ||||
|  | ||||
| Only thos devices in the list will have their Node IPs resolved to names using | ||||
| the DNS. You can include hostnames, IP addresses and subnets (IPv4 or IPv6) in | ||||
| the list. | ||||
|  | ||||
| =head3 C<store_wireless_clients> | ||||
|  | ||||
| Value: Boolean. Default: C<true>. | ||||
|   | ||||
| @@ -18,7 +18,7 @@ the L<documentation|App::Netdisco>. Then: | ||||
|  git clone git://git.code.sf.net/p/netdisco/netdisco-ng netdisco-ng | ||||
|  cd netdisco-ng/Netdisco | ||||
|    | ||||
|  ~/bin/localenv DBIC_TRACE_PROFILE=console DBIC_TRACE=1 plackup -R share,lib -p 5001 bin/netdisco-web-fg | ||||
|  DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv plackup -R share,lib -p 5001 bin/netdisco-web-fg | ||||
|  | ||||
| The above creates you a git clone (change the URL if you're a Netdisco | ||||
| Developer) and runs the web server: | ||||
| @@ -47,8 +47,17 @@ Restarts the web server when you save a file in the C<share> or C<lib> directory | ||||
|  | ||||
| =back | ||||
|  | ||||
| You should be able to work out something similar for | ||||
| C<bin/netdisco-daemon-fg>, too. Happy hacking! | ||||
| You might also want to set C<check_userlog> to C<true> in your config to quieten | ||||
| some of the web client callbacks. | ||||
|  | ||||
| For the daemon, it's very similar: | ||||
|  | ||||
|  DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv bin/netdisco-daemon-fg | ||||
|  | ||||
| Don't be alarmed by the high rate of database queries in the daemon - most of | ||||
| them are communicating only with a local in-memory SQLite database. | ||||
|  | ||||
| Happy hacking! | ||||
|  | ||||
| =head1 Introduction | ||||
|  | ||||
|   | ||||
| @@ -35,6 +35,21 @@ but they are backwards compatible. | ||||
|  | ||||
| =back | ||||
|  | ||||
| =head1 2.018000 | ||||
|  | ||||
| =head2 General Notices | ||||
|  | ||||
| The previous mentioned bug in Macsuck is now fixed. | ||||
|  | ||||
| During Arpnip, Node IPs are no longer resolved to DNS hostnames in real-time. | ||||
| Another job is queued to perform this action for the device. You can therefore | ||||
| control using the new C<nodenames_no> and C<nodenames_only> config parameters | ||||
| which daemons run this job. | ||||
|  | ||||
| The idea here is to support sites where the SNMP polling node has no useful | ||||
| DNS, but another system can update the DNS entries for nodes (yet do no | ||||
| polling). | ||||
|  | ||||
| =head1 2.017000 | ||||
|  | ||||
| =head2 General Notices | ||||
|   | ||||
| @@ -307,18 +307,18 @@ App::Netdisco: | ||||
|  | ||||
| =item  C<search_node> | ||||
|  | ||||
| A base url which links to the Node tab of the Search page, together with the | ||||
| correct default search options set. | ||||
| A path and query string which links to the Node tab of the Search page, | ||||
| together with the correct default search options set. | ||||
|  | ||||
| =item  C<search_device> | ||||
|  | ||||
| A base url which links to the Device tab of the Search page, together with the | ||||
| correct default search options set. | ||||
| A path and query string which links to the Device tab of the Search page, | ||||
| together with the correct default search options set. | ||||
|  | ||||
| =item  C<device_ports> | ||||
|  | ||||
| A base url which links to the Ports tab of the Device page, together with | ||||
| the correct default column view options set. | ||||
| A path and query sting which links to the Ports tab of the Device page, | ||||
| together with the correct default column view options set. | ||||
|  | ||||
| =item  C<uri_base> | ||||
|  | ||||
|   | ||||
| @@ -10,8 +10,10 @@ our @EXPORT = (); | ||||
| our @EXPORT_OK = qw/ | ||||
|   get_device | ||||
|   check_no | ||||
|   check_only | ||||
|   is_discoverable | ||||
|   is_arpnipable | ||||
|   can_nodenames | ||||
|   is_macsuckable | ||||
| /; | ||||
| our %EXPORT_TAGS = (all => \@EXPORT_OK); | ||||
| @@ -57,42 +59,11 @@ sub get_device { | ||||
|     ->find_or_new({ip => $ip}); | ||||
| } | ||||
|  | ||||
| =head2 check_no( $ip, $setting_name ) | ||||
|  | ||||
| Given the IP address of a device, returns true if the configuration setting | ||||
| C<$setting_name> matches that device, else returns false. | ||||
|  | ||||
| There are several options for what C<$setting_name> can contain: | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Hostname, IP address, IP prefix | ||||
|  | ||||
| =item * | ||||
|  | ||||
| C<"model:regex"> - matched against the device model | ||||
|  | ||||
| =item * | ||||
|  | ||||
| C<"vendor:regex"> - matched against the device vendor | ||||
|  | ||||
| =back | ||||
|  | ||||
| To simply match all devices, use IP Prefix "C<0.0.0.0/0>". All regular | ||||
| expressions are anchored (that is, they must match the whole string). | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub check_no { | ||||
|   my ($ip, $setting_name) = @_; | ||||
| sub _check_acl { | ||||
|   my ($ip, $config) = @_; | ||||
|   my $device = get_device($ip) or return 0; | ||||
|   my $addr = NetAddr::IP::Lite->new($device->ip); | ||||
|  | ||||
|   my $config = setting($setting_name) || []; | ||||
|   return 0 unless scalar @$config; | ||||
|  | ||||
|   foreach my $item (@$config) { | ||||
|       if ($item =~ m/^(.*)\s*:\s*(.*)$/) { | ||||
|           my $prop  = $1; | ||||
| @@ -116,6 +87,86 @@ sub check_no { | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| =head2 check_no( $ip, $setting_name ) | ||||
|  | ||||
| Given the IP address of a device, returns true if the configuration setting | ||||
| C<$setting_name> matches that device, else returns false. | ||||
|  | ||||
|  print "rejected!" if check_no($ip, 'discover_no'); | ||||
|  | ||||
| There are several options for what C<$setting_name> can contain: | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Hostname, IP address, IP prefix | ||||
|  | ||||
| =item * | ||||
|  | ||||
| C<"model:regex"> - matched against the device model | ||||
|  | ||||
| =item * | ||||
|  | ||||
| C<"vendor:regex"> - matched against the device vendor | ||||
|  | ||||
| =back | ||||
|  | ||||
| To simply match all devices, use "C<any>" or IP Prefix "C<0.0.0.0/0>". All | ||||
| regular expressions are anchored (that is, they must match the whole string). | ||||
| To match no devices we recommend an entry of "C<localhost>" in the setting. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub check_no { | ||||
|   my ($ip, $setting_name) = @_; | ||||
|  | ||||
|   my $config = setting($setting_name) || []; | ||||
|   return 0 unless scalar @$config; | ||||
|  | ||||
|   return _check_acl($ip, $config); | ||||
| } | ||||
|  | ||||
| =head2 check_only( $ip, $setting_name ) | ||||
|  | ||||
| Given the IP address of a device, returns false if the configuration setting | ||||
| C<$setting_name> matches that device, else returns true. | ||||
|  | ||||
|  print "rejected!" unless check_only($ip, 'discover_only'); | ||||
|  | ||||
| There are several options for what C<$setting_name> can contain: | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Hostname, IP address, IP prefix | ||||
|  | ||||
| =item * | ||||
|  | ||||
| C<"model:regex"> - matched against the device model | ||||
|  | ||||
| =item * | ||||
|  | ||||
| C<"vendor:regex"> - matched against the device vendor | ||||
|  | ||||
| =back | ||||
|  | ||||
| To simply match all devices, use "C<any>" or IP Prefix "C<0.0.0.0/0>". All | ||||
| regular expressions are anchored (that is, they must match the whole string). | ||||
| To match no devices we recommend an entry of "C<localhost>" in the setting. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub check_only { | ||||
|   my ($ip, $setting_name) = @_; | ||||
|  | ||||
|   my $config = setting($setting_name) || []; | ||||
|   return 1 unless scalar @$config; | ||||
|  | ||||
|   return _check_acl($ip, $config); | ||||
| } | ||||
|  | ||||
| =head2 is_discoverable( $ip, $device_type? ) | ||||
|  | ||||
| Given an IP address, returns C<true> if Netdisco on this host is permitted by | ||||
| @@ -147,7 +198,7 @@ sub is_discoverable { | ||||
|     if check_no($device, 'discover_no'); | ||||
|  | ||||
|   return _bail_msg("is_discoverable: device failed to match discover_only") | ||||
|     if check_no($device, 'discover_only'); | ||||
|     unless check_only($device, 'discover_only'); | ||||
|  | ||||
|   # cannot check last_discover for as yet undiscovered devices :-) | ||||
|   return 1 if not $device->in_storage; | ||||
| @@ -181,7 +232,7 @@ sub is_arpnipable { | ||||
|     if check_no($device, 'arpnip_no'); | ||||
|  | ||||
|   return _bail_msg("is_arpnipable: device failed to match arpnip_only") | ||||
|     if check_no($device, 'arpnip_only'); | ||||
|     unless check_only($device, 'arpnip_only'); | ||||
|  | ||||
|   return _bail_msg("is_arpnipable: cannot arpnip an undiscovered device") | ||||
|     if not $device->in_storage; | ||||
| @@ -195,6 +246,32 @@ sub is_arpnipable { | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| =head2 can_nodenames( $ip ) | ||||
|  | ||||
| Given an IP address, returns C<true> if Netdisco on this host is permitted by | ||||
| the local configuration to resolve Node IPs to DNS names for the device. | ||||
|  | ||||
| The configuration items C<nodenames_no> and C<nodenames_only> are checked | ||||
| against the given IP. | ||||
|  | ||||
| Returns false if the host is not permitted to do this job for the target | ||||
| device. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub can_nodenames { | ||||
|   my $ip = shift; | ||||
|   my $device = get_device($ip) or return 0; | ||||
|  | ||||
|   return _bail_msg("can_nodenames device matched nodenames_no") | ||||
|     if check_no($device, 'nodenames_no'); | ||||
|  | ||||
|   return _bail_msg("can_nodenames: device failed to match nodenames_only") | ||||
|     unless check_only($device, 'nodenames_only'); | ||||
|  | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| =head2 is_macsuckable( $ip ) | ||||
|  | ||||
| Given an IP address, returns C<true> if Netdisco on this host is permitted by | ||||
| @@ -215,7 +292,7 @@ sub is_macsuckable { | ||||
|     if check_no($device, 'macsuck_no'); | ||||
|  | ||||
|   return _bail_msg("is_macsuckable: device failed to match macsuck_only") | ||||
|     if check_no($device, 'macsuck_only'); | ||||
|     unless check_only($device, 'macsuck_only'); | ||||
|  | ||||
|   return _bail_msg("is_macsuckable: cannot macsuck an undiscovered device") | ||||
|     if not $device->in_storage; | ||||
|   | ||||
| @@ -44,22 +44,25 @@ MAC address is well-formed (according to common formats) | ||||
|  | ||||
| MAC address is not all-zero, broadcast, CLIP, VRRP or HSRP | ||||
|  | ||||
| =back | ||||
|  | ||||
| Optionally pass a cached set of Device port MAC addresses as the third | ||||
| argument, in which case an additional check is added: | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| MAC address does not belong to an interface on any known Device | ||||
|  | ||||
| =back | ||||
|  | ||||
| Optionally pass a cached set of Device port MAC addresses as the third | ||||
| argument, or else C<check_mac> will retrieve this for itself from the | ||||
| database. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub check_mac { | ||||
|   my ($device, $node, $port_macs) = @_; | ||||
|   $port_macs ||= get_port_macs($device); | ||||
|   my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0); | ||||
|   $port_macs ||= {}; | ||||
|  | ||||
|   # incomplete MAC addresses (BayRS frame relay DLCI, etc) | ||||
|   if ($mac->get_error) { | ||||
|   | ||||
| @@ -48,6 +48,7 @@ if (setting('extra_web_plugins') and ref [] eq ref setting('extra_web_plugins')) | ||||
| # workaround for https://github.com/PerlDancer/Dancer/issues/935 | ||||
| hook after_error_render => sub { setting('layout' => 'main') }; | ||||
|  | ||||
| # this hook should be loaded _after_ all plugins | ||||
| hook 'before_template' => sub { | ||||
|     my $tokens = shift; | ||||
|  | ||||
| @@ -61,6 +62,10 @@ hook 'before_template' => sub { | ||||
|     # access to logged in user's roles | ||||
|     $tokens->{user_has_role}  = sub { user_has_role(@_) }; | ||||
|  | ||||
|     # fix Plugin Template Variables to be only path+query | ||||
|     $tokens->{$_} = $tokens->{$_}->path_query | ||||
|       for qw/search_node search_device device_ports/; | ||||
|  | ||||
|     # allow very long lists of ports | ||||
|     $Template::Directive::WHILE_MAX = 10_000; | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,7 @@ foreach my $jobtype (keys %jobs_all, keys %jobs) { | ||||
|           if exists $jobs{$jobtype} and not param('device'); | ||||
|  | ||||
|         add_job($jobtype, param('device'), param('extra')); | ||||
|         redirect uri_for('/admin/jobqueue')->as_string; | ||||
|         redirect uri_for('/admin/jobqueue')->path; | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -159,7 +159,7 @@ get '/device' => require_login sub { | ||||
|     }); | ||||
|  | ||||
|     if (!defined $dev) { | ||||
|         return redirect uri_for('/', {nosuchdevice => 1})->as_string(); | ||||
|         return redirect uri_for('/', {nosuchdevice => 1})->path_query; | ||||
|     } | ||||
|  | ||||
|     params->{'tab'} ||= 'details'; | ||||
|   | ||||
| @@ -66,7 +66,7 @@ get '/search' => require_login sub { | ||||
|  | ||||
|     if (not param('tab')) { | ||||
|         if (not $q) { | ||||
|             return redirect uri_for('/')->as_string; | ||||
|             return redirect uri_for('/')->path; | ||||
|         } | ||||
|  | ||||
|         # pick most likely tab for initial results | ||||
| @@ -83,7 +83,7 @@ get '/search' => require_login sub { | ||||
|                       tab => 'details', | ||||
|                       q => ($nd->first->dns || $nd->first->ip), | ||||
|                       f => '', | ||||
|                     })->as_string; | ||||
|                     })->path_query; | ||||
|                 } | ||||
|  | ||||
|                 # multiple devices | ||||
|   | ||||
| @@ -86,6 +86,8 @@ macsuck_min_age: 0 | ||||
| arpnip_no: [] | ||||
| arpnip_only: [] | ||||
| arpnip_min_age: 0 | ||||
| nodenames_no: [] | ||||
| nodenames_only: [] | ||||
| store_wireless_clients: true | ||||
| store_modules: true | ||||
| ignore_interfaces: | ||||
|   | ||||
| @@ -14,3 +14,12 @@ body { | ||||
|   margin-left: 0px !important; | ||||
|   margin-right: 0px !important; | ||||
| } | ||||
|  | ||||
| .floatThead-table { | ||||
|   position: inherit; | ||||
|   top: 45px; | ||||
| } | ||||
|  | ||||
| a:after { | ||||
|   content: "" !important; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user