App-Netdisco

 view release on metacpan or  search on metacpan

lib/App/Netdisco/Worker/Plugin/Macsuck/Nodes.pm  view on Meta::CPAN

          next unless ref {} eq ref $map;

          foreach my $key (sort keys %$map) {
              # lhs matches device, rhs matches port
              next unless $key and $map->{$key};
              next unless acl_matches($device, $key);

              foreach my $port (sort { sort_port($a, $b) } keys %{ $device_ports }) {
                  next unless acl_matches($device_ports->{$port}, $map->{$key});

                  debug sprintf ' [%s] macsuck %s - port suppressed by macsuck_no_deviceports',
                    $device->ip, $port;
                  ++$ignoreport->{$port};
              }
          }
      }
  }


  foreach my $vlan (keys %{ $cache }) {
      foreach my $port (keys %{ $cache->{$vlan} }) {
          MAC: foreach my $mac (keys %{ $cache->{$vlan}->{$port} }) {

              unless (check_mac($mac, $device)) {
                  delete $cache->{$vlan}->{$port}->{$mac};
                  next MAC;
              }

              # this uses the cached $ports resultset to limit hits on the db
              my $device_port = $device_ports->{$port};

              # WRT #475 ... see? :-)
              unless (defined $device_port) {
                  debug sprintf
                    ' [%s] macsuck %s - port %s is not in database - skipping.',
                    $device->ip, $mac, $port;
                  delete $cache->{$vlan}->{$port}->{$mac};
                  next MAC;
              }

              if (exists $ignoreport->{$port}) {
                  debug sprintf
                    ' [%s] macsuck %s - port %s is suppressed by config - skipping.',
                    $device->ip, $mac, $port;
                  delete $cache->{$vlan}->{$port}->{$mac};
                  next MAC;
              }

              if (exists $neighborport->{$port}) {
                  debug sprintf
                    ' [%s] macsuck %s - seen another device thru port %s - skipping.',
                    $device->ip, $mac, $port;
                  delete $cache->{$vlan}->{$port}->{$mac};
                  next MAC;
              }

              # check to see if the port is connected to another device
              # and if we have that device in the database.

              # carefully be aware: "uplink" here means "connected to another device"
              # it does _not_ mean that the user wants nodes gathered on the remote dev.

              # we have two ways to detect "uplink" port status:
              #  * a neighbor was discovered using CDP/LLDP
              #  * a mac addr is seen which belongs to any device port/interface

              # allow to gather MACs on upstream (local) port for some kinds
              # of device that do not expose MAC address tables via SNMP.
              # relies on prefetched neighbors otherwise it would kill the DB
              # with device lookups.

              my $neigh_cannot_macsuck = eval { # can fail
                acl_matches(($device_port->neighbor || "0 but true"), 'macsuck_unsupported') ||
                match_to_setting($device_port->remote_type, 'macsuck_unsupported_type') };

              # here, is_uplink comes from Discover::Neighbors finding LLDP remnants
              if ($device_port->is_uplink) {
                  if ($neigh_cannot_macsuck) {
                      debug sprintf
                        ' [%s] macsuck %s - port %s neighbor %s without macsuck support',
                        $device->ip, $mac, $port,
                        (eval { $device_port->neighbor->ip }
                         || ($device_port->remote_ip
                             || $device_port->remote_id || '?'));
                      # continue!!
                  }
                  elsif (my $neighbor = $device_port->neighbor) {
                      debug sprintf
                        ' [%s] macsuck %s - port %s has neighbor %s - skipping.',
                        $device->ip, $mac, $port, $neighbor->ip;
                      delete $cache->{$vlan}->{$port}->{$mac};
                      next MAC;
                  }
                  elsif (my $remote = $device_port->remote_ip) {
                      debug sprintf
                        ' [%s] macsuck %s - port %s has undiscovered neighbor %s',
                        $device->ip, $mac, $port, $remote;
                      # continue!!
                  }
                  elsif (not setting('macsuck_bleed')) {
                      debug sprintf
                        ' [%s] macsuck %s - port %s is detected uplink - skipping.',
                        $device->ip, $mac, $port;

                      $neighborport->{$port} = [ $vlan, $mac ] # remember neighbor port mac
                        if exists $port_macs->{$mac};
                      delete $cache->{$vlan}->{$port}->{$mac};
                      next MAC;
                  }
              }

              # here, the MAC is known as belonging to a device switchport
              if (exists $port_macs->{$mac}) {
                  my $switch_ip = $port_macs->{$mac};
                  if ($device->ip eq $switch_ip) {
                      debug sprintf
                        ' [%s] macsuck %s - port %s connects to self - skipping.',
                        $device->ip, $mac, $port;
                      delete $cache->{$vlan}->{$port}->{$mac};
                      next MAC;
                  }

                  debug sprintf ' [%s] macsuck %s - port %s is probably an uplink',
                    $device->ip, $mac, $port;
                  $device_port->update({is_uplink => \'true'});

                  if ($neigh_cannot_macsuck) {
                      # neighbor exists and Netdisco can speak to it, so we don't want
                      # its MAC address. however don't add to neighborport as that would
                      # clear all other MACs on the port.
                      delete $cache->{$vlan}->{$port}->{$mac};
                      next MAC;
                  }

                  # when there's no CDP/LLDP, we only want to gather macs at the
                  # topology edge, hence skip ports with known device macs.
                  if (not setting('macsuck_bleed')) {
                        debug sprintf ' [%s] macsuck %s - port %s is at topology edge',
                            $device->ip, $mac, $port;

                        $neighborport->{$port} = [ $vlan, $mac ]; # remember for later
                        delete $cache->{$vlan}->{$port}->{$mac};
                        next MAC;
                  }
              }

              # possibly move node to lag master
              if (defined $device_port->slave_of
                    and exists $device_ports->{$device_port->slave_of}) {

                  my $parent = $device_port->slave_of;
                  $device_ports->{$parent}->update({is_uplink => \'true'});

                  # VLAN subinterfaces can be set uplink,
                  # but we don't want to move nodes there (so check is_master).
                  if ($device_ports->{$parent}->is_master) {
                      delete $cache->{$vlan}->{$port}->{$mac};
                      ++$cache->{$vlan}->{$parent}->{$mac};
                  }
              }
          }
      }
  }

  # restore MACs of neighbor devices.
  # this is when we have a "possible uplink" detected but we still want to
  # record the single MAC of the neighbor device so it works in Node search.
  foreach my $port (keys %$neighborport) {
      my ($vlan, $mac) = @{ $neighborport->{$port} };
      delete $cache->{$_}->{$port} for keys %$cache; # nuke nodes on all VLANs
      ++$cache->{$vlan}->{$port}->{$mac};
  }

  return $cache;
}

true;



( run in 0.563 second using v1.01-cache-2.11-cpan-39bf76dae61 )