diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/DeviceActions.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/DeviceActions.pm index 3a411f17..effab7a4 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/DeviceActions.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/DeviceActions.pm @@ -1,6 +1,7 @@ package App::Netdisco::Daemon::Worker::Interactive::DeviceActions; -use App::Netdisco::Util::Connect qw/snmp_connect get_device/; +use App::Netdisco::Util::SNMP ':all'; +use App::Netdisco::Util::Device 'get_device'; use App::Netdisco::Daemon::Worker::Interactive::Util ':all'; use Role::Tiny; diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/PortActions.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/PortActions.pm index 38e44f78..01c51740 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/PortActions.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/PortActions.pm @@ -1,7 +1,7 @@ package App::Netdisco::Daemon::Worker::Interactive::PortActions; -use App::Netdisco::Util::Connect ':all'; -use App::Netdisco::Util::Permissions ':all'; +use App::Netdisco::Util::SNMP ':all'; +use App::Netdisco::Util::Port ':all'; use App::Netdisco::Daemon::Worker::Interactive::Util ':all'; use Role::Tiny; diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/Util.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/Util.pm index b6844bbf..484cca5f 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/Util.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/Util.pm @@ -5,9 +5,7 @@ package App::Netdisco::Daemon::Worker::Interactive::Util; use base 'Exporter'; our @EXPORT = (); our @EXPORT_OK = qw/ done error /; -our %EXPORT_TAGS = ( - all => [qw/ done error /], -); +our %EXPORT_TAGS = (all => \@EXPORT_OK); sub done { return ('done', shift) } sub error { return ('error', shift) } diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm index fe791a48..0b20ee68 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm @@ -3,7 +3,7 @@ package App::Netdisco::Daemon::Worker::Manager; use Dancer qw/:moose :syntax :script/; use Dancer::Plugin::DBIC 'schema'; -use App::Netdisco::Util::DeviceProperties 'is_discoverable'; +use App::Netdisco::Util::Device 'is_discoverable'; use Net::Domain 'hostfqdn'; use Try::Tiny; diff --git a/Netdisco/lib/App/Netdisco/Manual/Developing.pod b/Netdisco/lib/App/Netdisco/Manual/Developing.pod index b18aa479..524af458 100644 --- a/Netdisco/lib/App/Netdisco/Manual/Developing.pod +++ b/Netdisco/lib/App/Netdisco/Manual/Developing.pod @@ -424,7 +424,7 @@ each). The daemon obviously needs to use L for device control. All the code for this has been factored out into the L namespace. -The L package provides for the creation of +The L package provides for the creation of SNMP::Info objects along with connection tests. So far, SNMPv3 is not supported. To enable trace logging of the SNMP::Info object simply set the C environment variable to a true value. The Connect library also diff --git a/Netdisco/lib/App/Netdisco/Util/Connect.pm b/Netdisco/lib/App/Netdisco/Util/Connect.pm deleted file mode 100644 index 09ffda61..00000000 --- a/Netdisco/lib/App/Netdisco/Util/Connect.pm +++ /dev/null @@ -1,153 +0,0 @@ -package App::Netdisco::Util::Connect; - -use Dancer qw/:syntax :script/; -use Dancer::Plugin::DBIC 'schema'; - -use SNMP::Info; -use Try::Tiny; -use Path::Class 'dir'; - -use base 'Exporter'; -our @EXPORT = (); -our @EXPORT_OK = qw/ - get_device get_port get_iid get_powerid snmp_connect -/; -our %EXPORT_TAGS = ( - all => [qw/ - get_device get_port get_iid get_powerid snmp_connect - /], -); - -=head1 App::Netdisco::Util::Connect - -A set of helper subroutines to support parts of the Netdisco application. - -There are no default exports, however the C<:all> tag will export all -subroutines. - -=head2 get_device( $ip ) - -Given an IP address, returns a L object for the Device in -the Netdisco database. The IP can be for any interface on the device. - -Returns C if the device or interface IP is not known to Netdisco. - -=cut - -sub get_device { - my $ip = shift; - - my $alias = schema('netdisco')->resultset('DeviceIp') - ->search({alias => $ip})->first; - return if not eval { $alias->ip }; - - return schema('netdisco')->resultset('Device') - ->find({ip => $alias->ip}); -} - -=head2 get_port( $device, $portname ) - -=cut - -sub get_port { - my ($device, $portname) = @_; - - # accept either ip or dbic object - $device = get_device($device) - if not ref $device; - - my $port = schema('netdisco')->resultset('DevicePort') - ->find({ip => $device->ip, port => $portname}); - - return $port; -} - -=head2 get_iid( $info, $port ) - -=cut - -sub get_iid { - my ($info, $port) = @_; - - # accept either port name or dbic object - $port = $port->port if ref $port; - - my $interfaces = $info->interfaces; - my %rev_if = reverse %$interfaces; - my $iid = $rev_if{$port}; - - return $iid; -} - -=head2 get_powerid( $info, $port ) - -=cut - -sub get_powerid { - my ($info, $port) = @_; - - # accept either port name or dbic object - $port = $port->port if ref $port; - - my $iid = get_iid($info, $port) - or return undef; - - my $p_interfaces = $info->peth_port_ifindex; - my %rev_p_if = reverse %$p_interfaces; - my $powerid = $rev_p_if{$iid}; - - return $powerid; -} - -=head2 snmp_connect( $ip ) - -Given an IP address, returns an L instance configured for and -connected to that device. The IP can be any on the device, and the management -interface will be connected to. - -Returns C if the connection fails. - -=cut - -sub snmp_connect { - my $ip = shift; - - # get device details from db - my $device = get_device($ip) - or return (); - - # TODO: really only supporing v2c at the moment - my %snmp_args = ( - DestHost => $device->ip, - Version => ($device->snmp_ver || setting('snmpver') || 2), - Retries => (setting('snmpretries') || 2), - Timeout => (setting('snmptimeout') || 1000000), - MibDirs => [ _build_mibdirs() ], - AutoSpecify => 1, - IgnoreNetSNMPConf => 1, - Debug => ($ENV{INFO_TRACE} || 0), - ); - - my $info = undef; - my $last_comm = 0; - COMMUNITY: foreach my $c (@{ setting('community_rw') || []}) { - try { - $info = SNMP::Info->new(%snmp_args, Community => $c); - ++$last_comm if ( - $info - and (not defined $info->error) - and length $info->uptime - ); - }; - last COMMUNITY if $last_comm; - } - - return $info; -} - -sub _build_mibdirs { - return map { dir(setting('mibhome'), $_) } - @{ setting('mibdirs') || [] }; -} - -1; diff --git a/Netdisco/lib/App/Netdisco/Util/DeviceProperties.pm b/Netdisco/lib/App/Netdisco/Util/Device.pm similarity index 60% rename from Netdisco/lib/App/Netdisco/Util/DeviceProperties.pm rename to Netdisco/lib/App/Netdisco/Util/Device.pm index cf7b2134..870627af 100644 --- a/Netdisco/lib/App/Netdisco/Util/DeviceProperties.pm +++ b/Netdisco/lib/App/Netdisco/Util/Device.pm @@ -1,34 +1,55 @@ -package App::Netdisco::Util::DeviceProperties; +package App::Netdisco::Util::Device; use Dancer qw/:syntax :script/; +use Dancer::Plugin::DBIC 'schema'; use NetAddr::IP::Lite ':lower'; -use App::Netdisco::Util::Connect 'get_device'; use base 'Exporter'; our @EXPORT = (); our @EXPORT_OK = qw/ + get_device is_discoverable - is_vlan_interface port_has_phone /; -our %EXPORT_TAGS = ( - all => [qw/ - is_discoverable - is_vlan_interface port_has_phone - /], -); +our %EXPORT_TAGS = (all => \@EXPORT_OK); -=head1 App::Netdisco::Util::DeviceProperties; +=head1 NAME + +App::Netdisco::Util::Device + +=head1 DESCRIPTION A set of helper subroutines to support parts of the Netdisco application. There are no default exports, however the C<:all> tag will export all subroutines. +=head1 FUNCTIONS + +=head2 get_device( $ip ) + +Given an IP address, returns a L object for the Device in +the Netdisco database. The IP can be for any interface on the device. + +Returns C if the device or interface IP is not known to Netdisco. + +=cut + +sub get_device { + my $ip = shift; + + my $alias = schema('netdisco')->resultset('DeviceIp') + ->search({alias => $ip})->first; + return if not eval { $alias->ip }; + + return schema('netdisco')->resultset('Device') + ->find({ip => $alias->ip}); +} + =head2 is_discoverable( $ip ) -Given an IP address, returns C if Netdisco on this host is permitted to -discover its configuration by the local configuration. +Given an IP address, returns C if Netdisco on this host is permitted by +the local configuration to discover the device. The configuration items C and C are checked against the given IP. @@ -65,32 +86,4 @@ sub is_discoverable { return 1; } -=head2 is_vlan_interface( $port ) - -=cut - -sub is_vlan_interface { - my $port = shift; - - my $is_vlan = (($port->type and - $port->type =~ /^(53|propVirtual|l2vlan|l3ipvlan|135|136|137)$/i) - or ($port->port and $port->port =~ /vlan/i) - or ($port->name and $port->name =~ /vlan/i)) ? 1 : 0; - - return $is_vlan; -} - -=head2 port_has_phone( $port ) - -=cut - -sub port_has_phone { - my $port = shift; - - my $has_phone = ($port->remote_type - and $port->remote_type =~ /ip.phone/i) ? 1 : 0; - - return $has_phone; -} - 1; diff --git a/Netdisco/lib/App/Netdisco/Util/Permissions.pm b/Netdisco/lib/App/Netdisco/Util/Permissions.pm deleted file mode 100644 index 96b6a9a7..00000000 --- a/Netdisco/lib/App/Netdisco/Util/Permissions.pm +++ /dev/null @@ -1,74 +0,0 @@ -package App::Netdisco::Util::Permissions; - -use Dancer qw/:syntax :script/; -use Dancer::Plugin::DBIC 'schema'; - -use App::Netdisco::Util::DeviceProperties ':all'; - -use base 'Exporter'; -our @EXPORT = (); -our @EXPORT_OK = qw/ - vlan_reconfig_check port_reconfig_check -/; -our %EXPORT_TAGS = ( - all => [qw/ - vlan_reconfig_check port_reconfig_check - /], -); - -=head1 App::Netdisco::Util::Permissions - -A set of helper subroutines to support parts of the Netdisco application. - -There are no default exports, however the C<:all> tag will export all -subroutines. - -=head2 vlan_reconfig_check( $port ) - -=cut - -sub vlan_reconfig_check { - my $port = shift; - my $ip = $port->ip; - my $name = $port->port; - - my $is_vlan = is_vlan_interface($port); - - # vlan (routed) interface check - return "forbidden: [$name] is a vlan interface on [$ip]" - if $is_vlan; - - return "forbidden: not permitted to change native vlan" - if not setting('vlanctl'); - - return; -} - -=head2 port_reconfig_check( $port ) - -=cut - -sub port_reconfig_check { - my $port = shift; - my $ip = $port->ip; - my $name = $port->port; - - my $has_phone = port_has_phone($port); - my $is_vlan = is_vlan_interface($port); - - # uplink check - return "forbidden: port [$name] on [$ip] is an uplink" - if $port->remote_type and not $has_phone and not setting('allow_uplinks'); - - # phone check - return "forbidden: port [$name] on [$ip] is a phone" - if $has_phone and setting('portctl_nophones'); - - # vlan (routed) interface check - return "forbidden: [$name] is a vlan interface on [$ip]" - if $is_vlan and not setting('portctl_vlans'); - - return; -} - -1; diff --git a/Netdisco/lib/App/Netdisco/Util/Port.pm b/Netdisco/lib/App/Netdisco/Util/Port.pm new file mode 100644 index 00000000..a49bf107 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Util/Port.pm @@ -0,0 +1,225 @@ +package App::Netdisco::Util::Port; + +use Dancer qw/:syntax :script/; +use Dancer::Plugin::DBIC 'schema'; + +use App::Netdisco::Util::Connect 'get_device'; + +use base 'Exporter'; +our @EXPORT = (); +our @EXPORT_OK = qw/ + vlan_reconfig_check port_reconfig_check + get_port get_iid get_powerid + is_vlan_interface port_has_phone +/; +our %EXPORT_TAGS = (all => \@EXPORT_OK); + +=head1 NAME + +App::Netdisco::Util::Port + +=head1 DESCRIPTION + +A set of helper subroutines to support parts of the Netdisco application. + +There are no default exports, however the C<:all> tag will export all +subroutines. + +=head1 FUNCTIONS + +=head2 vlan_reconfig_check( $port ) + +=over 4 + +=item * + +Sanity check that C<$port> is not a vlan subinterface. + +=item * + +Permission check that C is true in Netdisco config. + +=back + +Will return nothing if these checks pass OK. + +=cut + +sub vlan_reconfig_check { + my $port = shift; + my $ip = $port->ip; + my $name = $port->port; + + my $is_vlan = is_vlan_interface($port); + + # vlan (routed) interface check + return "forbidden: [$name] is a vlan interface on [$ip]" + if $is_vlan; + + return "forbidden: not permitted to change native vlan" + if not setting('vlanctl'); + + return; +} + +=head2 port_reconfig_check( $port ) + +=over 4 + +=item * + +Permission check that C is true in Netdisco config, if C<$port> +is an uplink. + +=item * + +Permission check that C is not true in Netdisco config, if +C<$port> has a phone connected. + +=item * + +Permission check that C is true if C<$port> is a vlan +subinterface. + +=back + +Will return nothing if these checks pass OK. + +=cut + +sub port_reconfig_check { + my $port = shift; + my $ip = $port->ip; + my $name = $port->port; + + my $has_phone = port_has_phone($port); + my $is_vlan = is_vlan_interface($port); + + # uplink check + return "forbidden: port [$name] on [$ip] is an uplink" + if $port->remote_type and not $has_phone and not setting('allow_uplinks'); + + # phone check + return "forbidden: port [$name] on [$ip] is a phone" + if $has_phone and setting('portctl_nophones'); + + # vlan (routed) interface check + return "forbidden: [$name] is a vlan interface on [$ip]" + if $is_vlan and not setting('portctl_vlans'); + + return; +} + +=head2 get_port( $device, $portname ) + +Given a device IP address and a port name, returns a L +object for the Port on the Device in the Netdisco database. + +The device IP can also be passed as a Device C object. + +Returns C if the device or port are not known to Netdisco. + +=cut + +sub get_port { + my ($device, $portname) = @_; + + # accept either ip or dbic object + $device = get_device($device) + if not ref $device; + + my $port = schema('netdisco')->resultset('DevicePort') + ->find({ip => $device->ip, port => $portname}); + + return $port; +} + +=head2 get_iid( $info, $port ) + +Given an L instance for a device, and the name of a port, returns +the current interface table index for that port. This can be used in further +SNMP requests on attributes of the port. + +Returns C if there is no such port name on the device. + +=cut + +sub get_iid { + my ($info, $port) = @_; + + # accept either port name or dbic object + $port = $port->port if ref $port; + + my $interfaces = $info->interfaces; + my %rev_if = reverse %$interfaces; + my $iid = $rev_if{$port}; + + return $iid; +} + +=head2 get_powerid( $info, $port ) + +Given an L instance for a device, and the name of a port, returns +the current PoE table index for the port. This can be used in further SNMP +requests on PoE attributes of the port. + +Returns C if there is no such port name on the device. + +=cut + +sub get_powerid { + my ($info, $port) = @_; + + # accept either port name or dbic object + $port = $port->port if ref $port; + + my $iid = get_iid($info, $port) + or return undef; + + my $p_interfaces = $info->peth_port_ifindex; + my %rev_p_if = reverse %$p_interfaces; + my $powerid = $rev_p_if{$iid}; + + return $powerid; +} + +=head2 is_vlan_interface( $port ) + +Returns true if the C<$port> L object represents a vlan +subinterface. + +This uses simple checks on the port I and I, and therefore might +sometimes returns a false-negative result. + +=cut + +sub is_vlan_interface { + my $port = shift; + + my $is_vlan = (($port->type and + $port->type =~ /^(53|propVirtual|l2vlan|l3ipvlan|135|136|137)$/i) + or ($port->port and $port->port =~ /vlan/i) + or ($port->name and $port->name =~ /vlan/i)) ? 1 : 0; + + return $is_vlan; +} + +=head2 port_has_phone( $port ) + +Returns true if the C<$port> L object has a phone connected. + +This uses a simple check on the I of the remote connected device, and +therefore might sometimes return a false-negative result. + +=cut + +sub port_has_phone { + my $port = shift; + + my $has_phone = ($port->remote_type + and $port->remote_type =~ /ip.phone/i) ? 1 : 0; + + return $has_phone; +} + +1; diff --git a/Netdisco/lib/App/Netdisco/Util/SNMP.pm b/Netdisco/lib/App/Netdisco/Util/SNMP.pm new file mode 100644 index 00000000..56be3f00 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Util/SNMP.pm @@ -0,0 +1,82 @@ +package App::Netdisco::Util::SNMP; + +use Dancer qw/:syntax :script/; +use App::Netdisco::Util::Device 'get_device'; + +use SNMP::Info; +use Try::Tiny; +use Path::Class 'dir'; + +use base 'Exporter'; +our @EXPORT = (); +our @EXPORT_OK = qw/ + snmp_connect +/; +our %EXPORT_TAGS = (all => \@EXPORT_OK); + +=head1 NAME + +App::Netdisco::Util::SNMP + +=head1 DESCRIPTION + +A set of helper subroutines to support parts of the Netdisco application. + +There are no default exports, however the C<:all> tag will export all +subroutines. + +=head1 FUNCTIONS + +=head2 snmp_connect( $ip ) + +Given an IP address, returns an L instance configured for and +connected to that device. The IP can be any on the device, and the management +interface will be connected to. + +Returns C if the connection fails. + +=cut + +sub snmp_connect { + my $ip = shift; + + # get device details from db + my $device = get_device($ip) + or return (); + + # TODO: only supporing v2c at the moment + my %snmp_args = ( + DestHost => $device->ip, + Version => ($device->snmp_ver || setting('snmpver') || 2), + Retries => (setting('snmpretries') || 2), + Timeout => (setting('snmptimeout') || 1000000), + MibDirs => [ _build_mibdirs() ], + AutoSpecify => 1, + IgnoreNetSNMPConf => 1, + Debug => ($ENV{INFO_TRACE} || 0), + ); + + my $info = undef; + my $last_comm = 0; + COMMUNITY: foreach my $c ($device->snmp_comm, @{ setting('community_rw') || []}) { + next unless defined $c and length $c; + try { + $info = SNMP::Info->new(%snmp_args, Community => $c); + ++$last_comm if ( + $info + and (not defined $info->error) + and length $info->uptime + ); + }; + last COMMUNITY if $last_comm; + } + + return $info; +} + +sub _build_mibdirs { + return map { dir(setting('mibhome'), $_) } + @{ setting('mibdirs') || [] }; +} + +1; diff --git a/Netdisco/lib/App/Netdisco/Util/Web.pm b/Netdisco/lib/App/Netdisco/Util/Web.pm index 290065a9..bbe286e8 100644 --- a/Netdisco/lib/App/Netdisco/Util/Web.pm +++ b/Netdisco/lib/App/Netdisco/Util/Web.pm @@ -5,19 +5,21 @@ our @EXPORT = (); our @EXPORT_OK = qw/ sort_port /; -our %EXPORT_TAGS = ( - all => [qw/ - sort_port - /], -); +our %EXPORT_TAGS = (all => \@EXPORT_OK); -=head1 App::Netdisco::Util::Web +=head1 NAME + +App::Netdisco::Util::Web + +=head1 DESCRIPTION A set of helper subroutines to support parts of the Netdisco application. There are no default exports, however the C<:all> tag will export all subroutines. +=head1 FUNCTIONS + =head2 sort_port( $a, $b ) Sort port names of various types used by device vendors. Interface is as