Group
Extension

App-Netdisco/lib/App/Netdisco/SSHCollector/Platform/EOS.pm

package App::Netdisco::SSHCollector::Platform::EOS;

=head1 NAME

App::Netdisco::SSHCollector::Platform::EOS

=head1 DESCRIPTION

Collect ARP entries from Arista EOS devices.

=cut

use strict;
use warnings;

use Dancer ':script';
use Moo;
use NetAddr::MAC qw/mac_as_ieee/;
use JSON qw(decode_json);

=head1 PUBLIC METHODS

=over 4

=item B<arpnip($host, $ssh)>

Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.

Returns a list of hashrefs in the format C<< { mac => MACADDR, ip => IPADDR } >>.

=back

=cut

my $if_name_map = {
  Vl => "Vlan",
  Lo => "Loopback",
  Eth => "Ethernet",
  Po => "Port-channel",
};

sub arpnip {
    my ($self, $hostlabel, $ssh, $args) = @_;
    debug "$hostlabel $$ arpnip()";

    my @arpentries;

    # ----- IPv4 -----
    my $cmd_v4 = "show ip arp vrf all | json | no-more\n";
    my @out_v4 = $ssh->capture({ stdin_data => $cmd_v4 });
    if (!$ssh->error) {
        my $data = eval { decode_json(join '', @out_v4) };
        if ($data && $data->{vrfs}) {
            foreach my $vrf (values %{ $data->{vrfs} }) {
                next unless $vrf->{ipV4Neighbors};
                foreach my $n (@{ $vrf->{ipV4Neighbors} }) {
                    next unless $n->{address} && $n->{hwAddress};

                    # some entries have multiple interfaces: "Vlan3134, Port-Channel46"
                    my @ifaces = split /\s*,\s*/, ($n->{interface} // '');
                    foreach my $iface (@ifaces) {
                        push @arpentries, {
                            ip  => $n->{address},
                            mac => $n->{hwAddress},
                            ( $iface ? (iface => $iface) : () ),
                        };
                    }
                }
            }
        }
    } else {
        info "$hostlabel $$ error running ARP command: " . $ssh->error;
    }

    # ----- IPv6 -----
    my $cmd_v6 = "show ipv6 neighbor vrf all | json | no-more\n";
    my @out_v6 = $ssh->capture({ stdin_data => $cmd_v6 });
    if (!$ssh->error) {
        my $data = eval { decode_json(join '', @out_v6) };
        if ($data && $data->{vrfs}) {
            foreach my $vrf (values %{ $data->{vrfs} }) {
                next unless $vrf->{ipV6Neighbors};
                foreach my $n (@{ $vrf->{ipV6Neighbors} }) {
                    next unless $n->{address} && $n->{hwAddress};
                    push @arpentries, { ip => $n->{address}, mac => $n->{hwAddress} };
                }
            }
        }
    } else {
        info "$hostlabel $$ error running IPv6 neighbor command: " . $ssh->error;
    }

    return @arpentries;
}
sub macsuck {
    my ($self, $hostlabel, $ssh, $args) = @_;

    unless ($ssh) {
        info "$hostlabel $$ macsuck() - no SSH session";
        return;
    }

    debug "$hostlabel $$ macsuck()";

    my $cmd = "show mac address-table | json | no-more\n";
    my @out = $ssh->capture({ stdin_data => $cmd });
    if ($ssh->error) {
        info "$hostlabel $$ error running command: " . $ssh->error;
        return;
    }

    my $json = join '', @out;
    my $data = eval { decode_json($json) };
    if ($@ or not $data->{unicastTable}->{tableEntries}) {
        info "$hostlabel $$ failed to parse JSON: $@";
        return;
    }

    my $macentries = {};

    foreach my $entry (@{ $data->{unicastTable}->{tableEntries} }) {
        my $vlan = $entry->{vlanId} // 0;
        my $mac  = mac_as_ieee($entry->{macAddress});
        my $port = $entry->{interface};

        # Skip bogus/no-port entries
        next unless $mac && $port && $port ne 'Router';


        ++$macentries->{$vlan}->{$port}->{$mac};

        debug sprintf "Parsed MAC vlan=%s mac=%s port=%s type=%s",
            $vlan, $mac, $port, $entry->{entryType};
    }

    return $macentries;
}

sub subnets {
    my ($self, $hostlabel, $ssh, $args) = @_;
    debug "$hostlabel $$ subnets()";

    my $cmd = "show ip route vrf all connected | json | no-more\n";
    my @out = $ssh->capture({ stdin_data => $cmd });
    if ($ssh->error) {
        info "$hostlabel $$ error running route command: " . $ssh->error;
        return;
    }

    my $data = eval { decode_json(join '', @out) };
    if ($@ or not $data->{vrfs}) {
        info "$hostlabel $$ failed to parse JSON routes: $@";
        return;
    }

    my @subnets;
    foreach my $vrf (values %{ $data->{vrfs} }) {
        foreach my $cidr (keys %{ $vrf->{routes} }) {
            next if $cidr =~ m/\/32$/; # skip host routes
            push @subnets, $cidr if $cidr =~ m{^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$};
        }
    }

    return @subnets;
}

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.