inverse-inc / packetfence

PacketFence is a fully supported, trusted, Free and Open Source network access control (NAC) solution. Boasting an impressive feature set including a captive-portal for registration and remediation, centralized wired and wireless management, powerful BYOD management options, 802.1X support, layer-2 isolation of problematic devices; PacketFence can be used to effectively secure networks small to very large heterogeneous networks.
https://packetfence.org
GNU General Public License v2.0
1.33k stars 281 forks source link

LLDP and CDP VoIP Detect for Switch Templates #5702

Open lyubomirtraykov opened 4 years ago

lyubomirtraykov commented 4 years ago

Add LDDP and CDP config in the templates.

julsemaan commented 4 years ago

This lacks a lot of details.

Also, implementing CDP/LLDP discovery works on a per-vendor basis with often multiple SNMP calls to figure it out. It will be hard to allow this configuration in the switch templates. The best we could probably do is to create some pre-built Perl templates for the different vendors that we could then apply on the switch template. But you'd be limited to the ones the Inverse team coded since its almost impossible to get the CDP/LLDP logic implemented through configuration

lyubomirtraykov commented 4 years ago

I disagree. This code is generic. It uses LLDP-MIB.

my $oid_lldpRemPortId  = '1.0.8802.1.1.2.1.4.1.1.7';
my $oid_lldpRemSysCapEnabled = '1.0.8802.1.1.2.1.4.1.1.12';

if ( !$self->connectRead() ) {
    return @phones;
}
$logger->trace(
    "SNMP get_next_request for lldpRemSysCapEnabled: $oid_lldpRemSysCapEnabled");
my $result = $self->{_sessionRead}
    ->get_table( -baseoid => $oid_lldpRemSysCapEnabled );
foreach my $oid ( keys %{$result} ) {
    if ( $oid =~ /^$oid_lldpRemSysCapEnabled\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ ) {
        if ( $ifIndex eq $2 ) {
            my $cache_lldpRemTimeMark     = $1;
            my $cache_lldpRemLocalPortNum = $2;
            my $cache_lldpRemIndex        = $3;
            if ( $self->getBitAtPosition($result->{$oid}, $SNMP::LLDP::TELEPHONE) ) {
                $logger->trace(
                    "SNMP get_request for lldpRemPortId: $oid_lldpRemPortId.$cache_lldpRemTimeMark.$cache_lldpRemLocalPortNum.$cache_lldpRemIndex"
                );
                my $MACresult = $self->{_sessionRead}->get_request(
                    -varbindlist => [
                        "$oid_lldpRemPortId.$cache_lldpRemTimeMark.$cache_lldpRemLocalPortNum.$cache_lldpRemIndex"
                    ]
                );
                if ($MACresult
                    && ($MACresult->{
                            "$oid_lldpRemPortId.$cache_lldpRemTimeMark.$cache_lldpRemLocalPortNum.$cache_lldpRemIndex"
                        }
                        =~ /^([0-9A-Z]{2})([0-9A-Z]{2})([0-9A-Z]{2})([0-9A-Z]{2})([0-9A-Z]{2})([0-9A-Z]{2})/i
                    )
                    )
                {
                    push @phones, lc("$1:$2:$3:$4:$5:$6");
                }
            }
        }
    }
}
return @phones;

We will only need Var for $position in getBitAtPosition and maybe one more Var for the regex in $MACresult

julsemaan commented 4 years ago

I see at least 2 different implementation in the code by looking at it quickly

Avaya != Cisco

And Procurve (which your example looks to be taken of) doesn't use the same regex for lldpRemIndex

I doubt we can get a one size fits all piece of code but if you find something that can work with all vendors using a handful of configuration variables, we can definitely get that in through a pull request

lyubomirtraykov commented 4 years ago

This regex ^(?:0x)?([0-9A-Z]{2})[\s-\,\.]?([0-9A-Z]{2})[\s-\,\.]?([0-9A-Z]{2})[\s-\,\.]?([0-9A-Z]{2})[\s-\,\.]?([0-9A-Z]{2})[\s-\,\.]?([0-9A-Z]{2})(?:.*)?(?::..)?$ should work for all vendors that you support now. This getBitAtPosition is universal:

sub getBitAtPosition {
    my ($bitStream, $position) = @_;
    my $bin ='';
    if ($bitStream =~ /^0x/) {
        $bitStream =~ s/^0x//i;
        $bin = join('',map { unpack("B4",pack("H",$_)) } (split //, $bitStream));
    } else {
        $bin = unpack('B*', $bitStream);
    }
    if (substr($bin,0,8) ne '00000000') {return substr($bin, $position, 1);}
    else {return substr($bin, $position -8, 1);}
}

The only var that i can think of now is $ifIndexOffset. For example: If $ifIndex = 501 and the $lldpRemLocalPortNum = 502, $ifIndexOffset should be 1 If $ifIndex = 501 and the $lldpRemLocalPortNum = 11502, $ifIndexOffset should be 11001 If $ifIndex = 501 and the $lldpRemLocalPortNum = 500, $ifIndexOffset should be -1 If $ifIndex = 501 and the $lldpRemLocalPortNum = 501, $ifIndexOffset should be 0

This way the code above gets universal. I did a test with Cisco, Brocade and Juniper and it works.

julsemaan commented 4 years ago

Still need other variables for Avaya/Nortel because they don't use the same MIB, not sure if they also use a different way to handle the data in the SNMP table

lyubomirtraykov commented 4 years ago

Ok we can have $uselldpRemSysDesc BOOL and $regexRemSysDesc regex.

lyubomirtraykov commented 4 years ago

This is an example code:

sub getPhonesLLDPAtIfIndex {
    my ( $self, $ifIndex ) = @_;
    my $logger = $self->logger;
    my @phones;
    if ( !$self->isVoIPEnabled() ) {
        $logger->debug( "VoIP not enabled on switch "
                . $self->{_ip}
                . ". getPhonesLLDPAtIfIndex will return empty list." );
        return @phones;
    }
    my $oid_lldpRemPortId  = '1.0.8802.1.1.2.1.4.1.1.7';
    my $oid_lldpRemSysDesc = '1.0.8802.1.1.2.1.4.1.1.10';
    my $oid_lldpRemSysCapEnabled = '1.0.8802.1.1.2.1.4.1.1.12';

    if ( !$self->connectRead() ) {
        return @phones;
    }
    $logger->trace(
        "SNMP get_next_request for lldpRemSysCapEnabled: $oid_lldpRemSysCapEnabled");
    my $result;
    if(isenabled($self->{_template}->{uselldpRemSysDesc})){
        $result = $self->{_sessionRead}
        ->get_table( -baseoid => $oid_lldpRemSysDesc );
    }
    else{
        my $result = $self->{_sessionRead}
        ->get_table( -baseoid => $oid_lldpRemSysCapEnabled );
    }
    foreach my $oid ( keys %{$result} ) {
        if ( $oid =~ /^$oid_lldpRemSysCapEnabled\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ ||
        $oid =~ /^$oid_lldpRemSysDesc\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ ) {
            if ( ($ifIndex + $ifIndexOffset) eq $2 ) {
                my $cache_lldpRemTimeMark     = $1;
                my $cache_lldpRemLocalPortNum = $2;
                my $cache_lldpRemIndex        = $3;

                if ( $self->getBitAtPosition($result->{$oid}, $SNMP::LLDP::TELEPHONE) ||
                    ($result->{$oid} =~ /$regexRemSysDesc/i and isenabled($self->{_template}->{uselldpRemSysDesc})) ) {
                    $logger->trace(
                        "SNMP get_request for lldpRemPortId: $oid_lldpRemPortId.$cache_lldpRemTimeMark.$cache_lldpRemLocalPortNum.$cache_lldpRemIndex"
                    );
                    my $MACresult = $self->{_sessionRead}->get_request(
                        -varbindlist => [
                            "$oid_lldpRemPortId.$cache_lldpRemTimeMark.$cache_lldpRemLocalPortNum.$cache_lldpRemIndex"
                        ]
                    );
                    if ($MACresult
                        && ($MACresult->{
                                "$oid_lldpRemPortId.$cache_lldpRemTimeMark.$cache_lldpRemLocalPortNum.$cache_lldpRemIndex"
                            }
                            =~ /^(?:0x)?([0-9A-Z]{2})[\s\-\,\.]?([0-9A-Z]{2})[\-\,\.\s]?([0-9A-Z]{2})[\s\-\,\.]?([0-9A-Z]{2})[\s\-\,\.]?([0-9A-Z]{2})[\s\-\,\.]?([0-9A-Z]{2})(?:.*)?(?::..)?$/i
                        )
                        )
                    {
                        push @phones, lc("$1:$2:$3:$4:$5:$6");
                    }
                }
            }
        }
    }
    return @phones;
}
julsemaan commented 4 years ago

Hi,

Since you've done quite a bit of work around it, I think it would be good if you submit a pull request.

This way we can test it with the switches we have available in our lab and continue the conversation there.

Thanks,