Anonymous | Login | 2024-08-24 20:57 EDT | ![]() |
Main | My View | View Issues | Change Log | Roadmap |
View Issue Details [ Jump to Notes ] | [ Issue History ] [ Print ] | |||||||
ID | Project | Category | View Status | Date Submitted | Last Update | |||
0001331 | PacketFence | core | public | 2011-11-09 12:42 | 2015-02-13 15:26 | |||
Reporter | packetfence4me | |||||||
Assigned To | obilodeau | |||||||
Priority | normal | Severity | feature | Reproducibility | always | |||
Status | closed | Resolution | open | |||||
Platform | OS | OS Version | ||||||
Product Version | 3.0.1 | |||||||
Target Version | Fixed in Version | |||||||
Summary | 0001331: Swtich in Disovery mode logs node connection details | |||||||
Description | What would be beneficial to our environment would be able to put a switch in discovery mode and it does the following - does not check the managed VLANs for that switch - logs the node location, and any VLAN from learnt trap, and IP - removes node active location from remove trap - triggers alert violations It would not - contact the switch to perform any SNMP writes (setVLAN, bouncepPort, etc) | |||||||
Additional Information | I've been able to accomplish this somewhat through the modification of the pfsetvlan file in the pf/sbin/ folder. I've marked the mods with comments of Josh Fisk. I'm not the most efficient programmer and I've done this through trial and error. However, my version does not look at the mode which the switch is in, it simply does this no matter the mode. Thanks, | |||||||
Tags | Code Review | |||||||
fixed in git revision | ||||||||
fixed in mtn revision | ||||||||
Attached Files | ![]() #!/usr/bin/perl -w # perldoc {{{1 =head1 NAME pfsetvlan =head1 SYNOPSIS ./pfsetvlan [options] Options: -daemonize daemonize -help brief help message -man full documentation =head1 DESCRIPTION Act on SNMP traps and set switch port VLAN according to the discovered MACs status in packetfence. =head1 AUTHOR Dominik Gehl <dgehl@inverse.ca> Regis Balzard <rbalzard@inverse.ca> Olivier Bilodeau <obilodeau@inverse.ca> =head1 COPYRIGHT Copyright (C) 2006-2011 Inverse inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =cut # }}}1 # use, require {{{1 use strict; use warnings; use diagnostics; use Data::Dumper; use File::Tail; use threads; use threads::shared; use File::Basename qw(basename); use Log::Log4perl; use Getopt::Long; use Pod::Usage; use Net::SMTP; use POSIX (); use constant INSTALL_DIR => '/usr/local/pf'; use constant { LOG_FILE => INSTALL_DIR . "/logs/packetfence.log", PFCMD_FILE => INSTALL_DIR . "/bin/pfcmd" }; use lib INSTALL_DIR . '/lib'; require 5.8.8; use pf::config; use pf::floatingdevice::custom; use pf::inline::custom $INLINE_API_LEVEL; use pf::locationlog; use pf::node; use pf::SNMP 2.00; use pf::SNMP::constants; use pf::SwitchFactory; use pf::traplog; use pf::util; use pf::violation; use pf::vlan::custom $VLAN_API_LEVEL; $thread = 1; # }}}1 Log::Log4perl->init_and_watch( INSTALL_DIR . '/conf/log.conf', $LOG4PERL_RELOAD_TIMER ); my $logger = Log::Log4perl->get_logger( basename($0) ); Log::Log4perl::MDC->put( 'proc', basename($0) ); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); # sighandler {{{1 POSIX::sigaction( &POSIX::SIGTERM, POSIX::SigAction->new( 'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER ) ) or $logger->logdie("pfsetvlan: could not set SIGTERM handler: $!"); POSIX::sigaction( &POSIX::SIGINT, POSIX::SigAction->new( 'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER ) ) or $logger->logdie("pfsetvlan: could not set SIGINT handler: $!"); POSIX::sigaction( &POSIX::SIGALRM, POSIX::SigAction->new( 'ignore_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER ) ) or $logger->logdie("pfsetvlan: could not set SIGALRM handler: $!"); # }}}1 # command line options {{{1 my $help; my $man; my $daemonize; GetOptions( "help|?" => \$help, "man" => \$man, "daemonize" => \$daemonize, ) or pod2usage( -verbose => 1 ); pod2usage( -verbose => 2 ) if $man; pod2usage( -verbose => 1 ) if $help; # }}}1 # startup {{{1 my $switchFactory = pf::SwitchFactory->getInstance(); # building trap-matcher regexp my $TRAP_PATTERN = qr/ ^\d{4}-\d{2}-\d{2}\|\d{2}:\d{2}:\d{2}\| # date|time (?:UDP:\ \[)? # Optional "UDP: [" (since v2 traps I think) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) # network device ip address (?:\]:\d+)? # Optional "]:port" (since v2 traps I think) (?:\-\>\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\])? # Optional "->[ip address]" (since net-snmp 5.4) \|([^|]*)\| # Used to carry network device ip if it's a local trap (.+)$ # Trap message /sx; # s for multiline support (if we encounter an Hex 0a which is encoded as a newline in STRING) my $fh = new File::Tail( 'name' => INSTALL_DIR . '/logs/snmptrapd.log', 'interval' => 2, 'reset_tail' => 0, 'maxinterval' => 2 ); if ($daemonize) { # Begin - Daemonize the program (see http://www.webreference.com/perl/tutorial/9/index.html) chdir('/') or $logger->logdie("Can't chdir to /: $!"); open( STDIN, '<', '/dev/null' ) or $logger->logdie("Can't read /dev/null: $!"); open STDERR, '>>', LOG_FILE or $logger->logdie( "Can't open " . LOG_FILE ); open STDOUT, '>>', LOG_FILE or $logger->logdie( "Can't open " . LOG_FILE ); # (at this point STDOUT and STDERR should be redirected to files) defined( my $pid = fork ) or $logger->logdie("Can't fork: $!"); POSIX::_exit(0) if $pid; POSIX::setsid() or $logger->logdie("Can't start a new session: $!"); umask 0; # Write the PID if ( !createpid() ) { $logger->logdie("unable to daemonize"); } } # End - Daemonize... $logger->info("Process started"); my %threadList_running : shared; my %threadList_toBeKilled : shared; my @threadList_queued : shared; my @trapList_queued : shared; my $switchFactory_locker : shared; my @completeThreadList; my %switch_locker : shared; foreach my $switch_ip ( sort keys %{ $switchFactory->{_config} } ) { if ( $switch_ip ne 'default' ) { $switch_locker{$switch_ip} = &share( {} ); } } for ( my $i = 1; $i <= $Config{'vlan'}{'nbtraphandlerthreads'}; $i++ ) { my $t = threads->new("signalHandlerThreadListQueued"); if ( !defined($t) ) { $logger->error( "could not create " . $i . ( ( $i == 1 ) ? "st" : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) ) ) . " signalHandlerThreadListQueued thread" ); exit -1; } else { push @completeThreadList, $t; $logger->debug( "created " . $i . ( ( $i == 1 ) ? "st" : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) ) ) . " signalHandlerThreadListQueued thread" ); } } for ( my $i = 1; $i <= $Config{'vlan'}{'nbtrapparserthreads'}; $i++ ) { my $t = threads->new("signalHandlerTrapListQueued"); if ( !defined($t) ) { $logger->error( "could not create " . $i . ( ( $i == 1 ) ? "st" : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) ) ) . " signalHandlerTrapListQueued thread" ); exit -1; } else { push @completeThreadList, $t; $logger->debug( "created " . $i . ( ( $i == 1 ) ? "st" : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) ) ) . " signalHandlerTrapListQueued thread" ); } } # }}}1 # main program - read trap lines and addTrapLineToQueue($trapLine) {{{1 my $currentTrapLine = undef; my $completeTrapLine; my $inMultiLineTrap = 0; while ( defined( $currentTrapLine = $fh->read ) ) { $currentTrapLine =~ s/\r\n/\n/; chomp($currentTrapLine); if ( $currentTrapLine =~ m/BEGIN VARIABLEBINDINGS/ ) { if ( $currentTrapLine =~ m/END VARIABLEBINDINGS$/ ) { addTrapLineToQueue($currentTrapLine); } else { #start multiLine read $inMultiLineTrap = 1; $completeTrapLine = $currentTrapLine; } } else { if ($inMultiLineTrap) { $completeTrapLine .= "\n$currentTrapLine"; if ( $currentTrapLine =~ m/END VARIABLEBINDINGS$/ ) { #end multiLine read $inMultiLineTrap = 0; addTrapLineToQueue($completeTrapLine); } } else { $logger->warn("ignoring non trap line $currentTrapLine"); } } } # }}}1 # sub addTrapLineToQueue($trapLine) - cond_signal(@trapList_queued){{{1 sub addTrapLineToQueue { my ($trapLine) = @_; my $logger = Log::Log4perl->get_logger('pfsetvlan::parsing'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); $lockLogger->trace("locking - trying to lock trapList_queued"); { lock @trapList_queued; $logger->debug("adding trapline $trapLine to queued trapList"); push @trapList_queued, "raw|$trapLine"; $lockLogger->trace("locking - sending signal for trapList_queued"); cond_signal(@trapList_queued); } $lockLogger->trace("locking - unlocked trapList_queued"); } # }}}1 # sub signalHandlerTrapListQueued - cond_wait(@trapList_queued), parseTrap($trapLineToParse) {{{1 sub signalHandlerTrapListQueued { my $logger = Log::Log4perl->get_logger('pfsetvlan::parsing'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); while (1) { my $trapLineToParse = undef; my $parseResult = undef; $lockLogger->trace( "locking - trying to lock trapList_queued in signalHandlerTrapListQueued" ); { lock @trapList_queued; $lockLogger->trace( "locking - obtained lock on trapList_queued in signalHandlerTrapListQueued" ); $lockLogger->trace( "locking - waiting for signal for trapList_queued in signalHandlerTrapListQueued" ); cond_wait(@trapList_queued); $lockLogger->trace( "locking - received signal for trapList_queued in signalHandlerTrapListQueued" ); $logger->trace( "initially trapList_queued contains\n" . join( "\n", @trapList_queued ) . "\n" ); #find first entry in @trapList_queued which must be parsed my $i = 0; while (( $i < scalar(@trapList_queued) ) && ( !( $trapList_queued[$i] =~ /^raw\|/ ) ) ) { $logger->trace( "did not find raw trapLine in trapList_queued at position $i" ); $i++; } if ( $i < scalar(@trapList_queued) ) { $trapLineToParse = substr( $trapList_queued[$i], 4 ); $logger->debug( "retrieved raw trapline $trapLineToParse from trapList_queued at position $i" ); $trapList_queued[$i] = "inparse" . threads->self->tid() . "|$trapLineToParse"; $logger->trace( "after retrieval operation trapList_queued contains\n" . join( "\n", @trapList_queued ) . "\n" ); } else { $logger->trace( "couldn't find any raw trapLine in trapList_queued"); } } $lockLogger->trace( "locking - unlocked trapList_queued in signalHandlerTrapListQueued" ); if ( defined($trapLineToParse) ) { $logger->debug("calling parseTrap for $trapLineToParse"); $parseResult = parseTrap($trapLineToParse); $lockLogger->trace( "locking - trying to lock trapList_queued in signalHandlerTrapListQueued" ); { lock @trapList_queued; $lockLogger->trace( "locking - obtained lock on trapList_queued in signalHandlerTrapListQueued" ); my $i = 0; my $threadId = threads->self->tid(); $logger->trace( "after parseTrap operation trapList_queued contains\n" . join( "\n", @trapList_queued ) . "\n" ); while (( $i < scalar(@trapList_queued) ) && ( !( $trapList_queued[$i] =~ /^inparse$threadId\|/ ) ) ) { $logger->trace( "did not find line starting with \"inparse$threadId\" in trapList_queued at position $i" ); $i++; } if ( $i < scalar(@trapList_queued) ) { $logger->trace( "replacing threadList_queued[$i] with parseTrap result" ); $trapList_queued[$i] = "parsed$threadId|" . ( $parseResult || '' ); $logger->trace( "after parseTrap and replacement operation trapList_queued contains\n" . join( "\n", @trapList_queued ) . "\n" ); $logger->debug( "finished parsing $i" . ( ( $i == 2 ) ? 'nd' : ( $i == 3 ) ? 'rd' : 'th' ) . " trapList_queued entry" ); } else { $logger->warn( "could not find line starting with \"inparse$threadId\" in trapList_queued -> this is BAD" ); } } $lockLogger->trace( "locking - unlocked trapList_queued in signalHandlerTrapListQueued" ); } $lockLogger->trace( "locking - trying to lock trapList_queued in signalHandlerTrapListQueued" ); { lock @trapList_queued; $lockLogger->trace( "locking - obtained lock on trapList_queued in signalHandlerTrapListQueued" ); #for already parsed entries at the begining of @trapList_queued # shift and check if we need to add them to @threadList_queued my $i = 0; my @candidatesForThreadList_queued; my @tmpTrapList_queued; my %switchesWithUnfinishedParse; while (( $i < scalar(@trapList_queued) ) && ( !( $trapList_queued[$i] =~ /^raw\|/ ) ) ) { my $trapLine = undef; my $prefix = undef; if ( $trapList_queued[$i] =~ /^(parsed\d+\|)/ ) { $prefix = $1; } elsif ( $trapList_queued[$i] =~ /^(inparse\d+\|)/ ) { $prefix = $1; } if ( defined($prefix) ) { $trapLine = substr( $trapList_queued[$i], length($prefix) ); if ( $prefix =~ /^inparse/ ) { push @tmpTrapList_queued, $trapList_queued[$i]; if ($trapLine =~ /$TRAP_PATTERN/) { my $switch_ip = $1; $logger->trace( "adding $switch_ip to switchesWithUnfinishedParse" ); $switchesWithUnfinishedParse{$switch_ip} = 1; } } else { if ( length($trapLine) > 0 ) { my ( $switch_ip, $switch_port, $trapType, $trapVlan, $trapOperation, $trapMac ) = split( /\|/, $trapLine ); if (exists( $switchesWithUnfinishedParse{$switch_ip} ) ) { $logger->trace( "parsed trapLine $trapLine with prefix $prefix comes after a trapLine for the same switch which is still being parsed. Keeping it in trapList_queued" ); push @tmpTrapList_queued, $trapList_queued[$i]; } else { $logger->trace( "retrieved parsed trapLine $trapLine with prefix $prefix from trapList_queued. Adding it to candidatesForThreadList_queued" ); push @candidatesForThreadList_queued, $trapLine; } } } } $i++; } while ( $i < scalar(@trapList_queued) ) { push @tmpTrapList_queued, $trapList_queued[$i]; $i++; } @trapList_queued = @tmpTrapList_queued; $logger->trace( "after intelligent removal of parsed traps trapList_queued contains\n" . join( "\n", @trapList_queued ) . "\n" ); if ( scalar(@candidatesForThreadList_queued) > 0 ) { $lockLogger->trace( "locking - trying to lock threadList_queued in signalHandlerTrapListQueued" ); { lock @threadList_queued; $lockLogger->trace( "locking - obtained lock on threadList_queued in signalHandlerTrapListQueued" ); foreach my $trapLine (@candidatesForThreadList_queued) { $logger->debug( "validating if parsed trapLine $trapLine should be added to threadList_queued" ); my ( $switch_ip, $switch_port, $trapType, $trapVlan, $trapOperation, $trapMac ) = split( /\|/, $trapLine ); my $switch = $switchFactory->instantiate($switch_ip); if (!$switch) { $logger->error("Can not instantiate switch $switch_ip !"); next; } if (( $trapType eq 'secureMacAddrViolation' ) && (grep( { /\Q$switch_ip|$switch_port|$trapType|\E/ } @threadList_queued ) != 0) ) { $logger->info( "$trapType trap already in the queue for $switch_ip ifIndex $switch_port. Won't add another one"); # we had some side effects while testing the floating device thing. So we added this. # this is necessary as long as we have not fixed the ugly sleep(5) patch. See floatingdevice.pm # and pf::SNMP::Cisco::Catalyst_2950 } elsif (( $trapType eq 'secureMacAddrViolation' ) && (! $switch->isPortSecurityEnabled($switch_port)) ) { $logger->info("$trapType trap on $switch_ip ifIndex $switch_port. Port Security is no " . "longer configured on the port. Flush the trap"); # #Revised Section by Josh Fisk for stealth mode 11032011 # # # } elsif (( $trapType eq 'mac' ) # && (grep( { $_ == $trapVlan } @{ $switch->{_vlans} } ) == 0 ) ) { # $logger->info( # "$trapType trap for VLAN $trapVlan on $switch_ip ifIndex $switch_port. We don't manage this VLAN. Flush the trap" # ); # #Revised Section by Josh Fisk for stealth mode 11032011 # # } elsif ( ( $trapType eq 'mac' ) && ( $trapVlan ne $switch->getVlan($switch_port) ) ) { $logger->info( "$trapOperation trap for VLAN $trapVlan on $switch_ip ifIndex $switch_port. This port is no longer in this VLAN. Flush the trap" ); } else { push @threadList_queued, $trapLine; $logger->debug( "added trap $trapType at $switch_ip ifindex $switch_port to queued threadList" ); traplog_insert( $switch_ip, $switch_port, $trapType ); $lockLogger->trace( "locking - sending signal for threadList_queued in signalHanderTrapListQueued" ); cond_signal(@threadList_queued); } } } $lockLogger->trace( "locking - unlocked threadList_queued in signalHandlerTrapListQueued" ); } #signal if we still have 'raw' entries in trapList_queued if ( grep ( {/^raw\|/} @trapList_queued ) > 0 ) { $lockLogger->trace( "locking - sending signal for trapList_queued in signalHanderTrapListQueued" ); cond_signal(@trapList_queued); } } $lockLogger->trace( "locking - unlocked trapList_queued in signalHandlerTrapListQueued" ); } } # }}}1 # sub parseTrap {{{1 sub parseTrap { my ($trapLine) = @_; my $logger = Log::Log4perl->get_logger('pfsetvlan::parsing'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); $logger->debug("parsing trap $trapLine"); if ( $trapLine =~ /$TRAP_PATTERN/ ) { #test if this is a 'normal' trap (i.e. a trap sent from a switch #or a local trap (INVERSE-PACKETFENCE-NOTIFICATION-MIB) my $switch; my $switch_ip; my $isLocalTrap = ( ( $1 eq '127.0.0.1' ) || ( $3 =~ /BEGIN VARIABLEBINDINGS \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0 = OID: \.1\.3\.6\.1\.4\.1\.29464\.1/ ) ); $lockLogger->trace("locking - trying to lock switchFactory_locker in parseTrap"); if ($isLocalTrap) { lock $switchFactory_locker; $lockLogger->trace("locking - obtained lock on switchFactory_locker in parseTrap"); $switch_ip = $2; $logger->info("local (127.0.0.1) trap for switch $switch_ip"); $switch = $switchFactory->instantiate($switch_ip); } else { lock $switchFactory_locker; $lockLogger->trace("locking - obtained lock on switchFactory_locker in parseTrap"); my %config = %{ $switchFactory->{_config} }; $switch_ip = $1; if ( !exists $config{$switch_ip} ) { $logger->warn("We have received a trap from switch $switch_ip. This switch is UNREGISTERED. Flush the trap"); return; } if ( $config{$switch_ip}{'mode'} eq 'ignore' ) { $logger->info("We have received a trap from switch $switch_ip. This switch is in 'ignore' mode. Flush the trap"); return; } $switch = $switchFactory->instantiate($switch_ip); } $lockLogger->trace( "locking - unlocked switchFactory_locker in parseTrap"); if (!$switch) { $logger->error("Can not instantiate switch $switch_ip !"); } else { my $trapHashRef; # TODO push this out in pf::SNMP::PacketFence, no? if ($isLocalTrap) { my $trapLine = $3; if ($trapLine =~ /BEGIN\ VARIABLEBINDINGS\ # metadata \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0\ =\ OID:\ \.1\.3\.6\.1\.4\.1\.29464\.1\.1 # metadata \|\.1\.3\.6\.1\.2\.1\.2\.2\.1\.1\.(\d+)\ =\ INTEGER:\ \d+ # ifIndex \|\.1\.3\.6\.1\.2\.1\.2\.2\.1\.1\.\d+\ =\ INTEGER:\ (\d+) # connection type /x) { # WARNING: using trap operation for something it is not meant to $trapHashRef = { 'trapType' => 'reAssignVlan', 'trapIfIndex' => $1, 'trapOperation' => $2, }; } elsif ($trapLine =~ /BEGIN VARIABLEBINDINGS \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0 = OID: \.1\.3\.6\.1\.4\.1\.29464\.1\.1\|\.1\.3\.6\.1\.2\.1\.2\.2\.1\.1\.(\d+) = INTEGER:/) { $trapHashRef = { 'trapType' => 'reAssignVlan', 'trapIfIndex' => $1, }; } elsif ($trapLine =~ /BEGIN\ VARIABLEBINDINGS\ # metadata \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0\ =\ OID:\ \.1\.3\.6\.1\.4\.1\.29464\.1\.2 # metadata \|\.1\.3\.6\.1\.4\.1\.29464\.1\.3\ =\ STRING:\ \"(.+)\" # mac \|\.1\.3\.6\.1\.4\.1\.29464\.1\.4\ =\ INTEGER:\ (\d+)\ # connection_type END\ VARIABLEBINDINGS/x) { # WARNING: using trap operation for something it is not meant to # WARNING: always leave trapIfIndex because the trapLine parser expects it $trapHashRef = { 'trapType' => 'desAssociate', 'trapIfIndex' => 'WIFI', 'trapMac' => $1, 'trapOperation' => $2 }; } elsif ($trapLine =~ /BEGIN\ VARIABLEBINDINGS\ # metadata \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0\ =\ OID:\ \.1\.3\.6\.1\.4\.1\.29464\.1\.3 # metadata \|\.1\.3\.6\.1\.4\.1\.29464\.1\.3\ =\ STRING:\ \"(.+)\"\ # mac END\ VARIABLEBINDINGS/x) { # WARNING: using trap operation for something it is not meant to # WARNING: always leave trapIfIndex because the trapLine parser expects it $trapHashRef = { 'trapType' => 'firewallRequest', 'trapIfIndex' => 'NA', 'trapMac' => $1, }; } elsif ($trapLine =~ /BEGIN VARIABLEBINDINGS \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0 = OID: \.1\.3\.6\.1\.4\.1\.29464\.1\.2\|\.1\.3\.6\.1\.4\.1\.29464\.1\.3 = STRING: \"(.+)\" END VARIABLEBINDINGS/) { # WARNING: always leave trapIfIndex because the trapLine parser expects it $trapHashRef = { 'trapType' => 'desAssociate', 'trapIfIndex' => 'WIFI', 'trapMac' => $1 }; } elsif ( $trapLine =~ /\.1\.3\.6\.1\.4\.1\.45\.1\.6\.5\.3\.12\.1\.3\.(\d+)\.(\d+) = STRING: "([0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2})"/ ) { $trapHashRef->{'trapType'} = 's5SbsViolation'; $trapHashRef->{'trapIfIndex'} = ( $1 - 1 ) * 64 + $2; $trapHashRef->{'trapMac'} = lc($3); $trapHashRef->{'trapMac'} =~ s/ /:/g; $trapHashRef->{'trapVlan'} = $switch->getVlan( $trapHashRef->{'trapIfIndex'} ); } else { $trapHashRef->{'trapType'} = 'unknown'; } } else { $trapHashRef = $switch->parseTrap($3); } my $trapType = $trapHashRef->{'trapType'}; # skip unknown traps if ( $trapType ne 'unknown' ) { my $switch_ip = $switch->{_ip}; my $switch_port = $trapHashRef->{'trapIfIndex'}; my $trapVlan = ''; my $trapOperation = ''; my $trapMac = ''; if ( $trapType eq 'mac' ) { $trapVlan = $trapHashRef->{'trapVlan'}; $trapOperation = $trapHashRef->{'trapOperation'}; $trapMac = $trapHashRef->{'trapMac'}; if ( $trapOperation eq 'unknown' ) { $logger->info("ignoring unknown trap: $trapLine"); return; } } elsif ( $trapType eq 'dot11Deauthentication' ) { $trapMac = $trapHashRef->{'trapMac'}; # WARNING: because trapLine parser expects a switch_port entry, we provide him an irrelevant # one here but no one shall consume it anymore (since 2.0 refactoring) $switch_port = 'WIFI'; } elsif ( ( $trapType eq 'secureMacAddrViolation' ) || ( $trapType eq 'secureDynamicMacAddrViolation' ) ) { $trapMac = $trapHashRef->{'trapMac'}; $trapVlan = $trapHashRef->{'trapVlan'}; } elsif ( $trapType eq 'desAssociate' ) { $trapMac = $trapHashRef->{'trapMac'}; $trapOperation = $trapHashRef->{'trapOperation'}; } elsif ( $trapType eq 'firewallRequest' ) { $trapMac = $trapHashRef->{'trapMac'}; } elsif ($trapType eq 'reAssignVlan') { $trapOperation = $trapHashRef->{'trapOperation'}; } return "$switch_ip|$switch_port|$trapType|$trapVlan|$trapOperation|$trapMac"; } else { $logger->info("ignoring unknown trap: $trapLine"); } } } else { $logger->info("ignoring non trap line: $trapLine"); } return; } # }}}1 # sub signalHandlerThreadListQueued {{{1 sub signalHandlerThreadListQueued { my $logger = Log::Log4perl->get_logger('pfsetvlan::parsing'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); while (1) { my $mustDoSomeThing = 0; $lockLogger->trace( "locking - trying to lock threadList_queued in signalHandlerThreadListQueued" ); { lock @threadList_queued; $lockLogger->trace( "locking - obtained lock on threadList_queued in signalHandlerThreadListQueued" ); $lockLogger->trace( "locking - waiting for signal for threadList_queued in signalHandlerThreadListQueued" ); cond_wait(@threadList_queued); $lockLogger->trace( "locking - received signal for threadList_queued in signalHandlerThreadListQueued" ); $mustDoSomeThing = ( scalar(@threadList_queued) > 0 ); } $lockLogger->trace( "locking - unlocked threadList_queued in signalHandlerThreadListQueued" ); if ($mustDoSomeThing) { $logger->debug( "new enqueue or dequeue operation happened and threadList_queued is not empty" ); startTrapHandlers(); } } } # }}}1 # sub startTrapHandlers {{{1 sub startTrapHandlers { my $logger = Log::Log4perl->get_logger('pfsetvlan::handling'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); my $mustDoSomeThing = 0; my $switch_ip; my $switch_port; my $trapType; my $trapVlan; my $trapOperation; my $trapMac; $lockLogger->trace( "locking - trying to lock threadList_queued, threadList_running and treadList_toBeKilled in startTrapHandlers" ); { lock @threadList_queued; lock %threadList_running; lock %threadList_toBeKilled; $lockLogger->trace( "locking - obtained lock on threadList_queued, threadList_running and treadList_toBeKilled in startTrapHandlers" ); my @tmp = (); my $nbThreadsRunning = scalar( keys(%threadList_running) ); my $threadList_queued_txt = ''; foreach my $trapLineWithAdditionalInfo (@threadList_queued) { #extract information from trap line if ($trapLineWithAdditionalInfo =~ /^([^|]+)\|([^|]+)\|([^|]+)\|(.+)$/) { $threadList_queued_txt .= "\n$1\t$2\t$3"; } else { $logger->warn("unable to parse trapLine.. here's the line: $trapLineWithAdditionalInfo"); } } my $threadList_running_txt = ''; foreach my $threadList_running_tmp ( keys %threadList_running ) { $threadList_running_tmp =~ /^(.+)\|(.+)$/; $threadList_running_txt .= "\n$1\t$2"; } $logger->info( "nb of items in queue: " . scalar(@threadList_queued) . "; nb of threads running: $nbThreadsRunning" ); $logger->debug("items in queue: $threadList_queued_txt"); $logger->debug("threads running: $threadList_running_txt"); foreach my $trapLineWithAdditionalInfo (@threadList_queued) { #extract information from trap line $trapLineWithAdditionalInfo =~ /^(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)$/; my $tmpSwitch_ip = $1; my $tmpSwitch_port = $2; my $tmpTrapType = $3; my $tmpTrapVlan = $4; my $tmpTrapOperation = $5; my $tmpTrapMac = $6; if (( !$mustDoSomeThing ) && (!exists( $threadList_running{"$tmpSwitch_ip|$tmpSwitch_port"} ) ) ) { #don't remove the " in the previous line #they are absolutely necessary for the code to work $switch_ip = $tmpSwitch_ip; $switch_port = $tmpSwitch_port; $trapType = $tmpTrapType; $trapVlan = $tmpTrapVlan; $trapOperation = $tmpTrapOperation; $trapMac = $tmpTrapMac; $mustDoSomeThing = 1; $threadList_running{"$switch_ip|$switch_port"} = $trapType; } else { push @tmp, $trapLineWithAdditionalInfo; #check if we have to send a kill signal to another trap #(this is the case if we got a 'down' trap and are still #handling an 'up' thread for the same switch port) if (( $tmpTrapType eq 'down' ) && (exists( $threadList_running{ "$tmpSwitch_ip|$tmpSwitch_port"} ) ) && ( $threadList_running{"$tmpSwitch_ip|$tmpSwitch_port"} eq 'up' ) && (!exists( $threadList_toBeKilled{ "$tmpSwitch_ip|$tmpSwitch_port"} ) ) ) { $logger->info( "down after up - sending kill signal to thread for $tmpSwitch_ip ifIndex $tmpSwitch_port" ); $threadList_toBeKilled{"$tmpSwitch_ip|$tmpSwitch_port"} = 1; } } } @threadList_queued = @tmp; } $lockLogger->trace( "locking - unlocked threadList_queued, threadList_running and treadList_toBeKilled in startTrapHandlers" ); if ($mustDoSomeThing) { $logger->debug("calling handleTrap"); handleTrap( $switch_ip, $switch_port, $trapType, $trapVlan, $trapOperation, $trapMac ); } } # }}}1 # sub handleTrap {{{1 sub handleTrap { # initialize {{{2 my ( $switch_ip, $switch_port, $trapType, $trapVlan, $trapOperation, $trapMac ) = @_; my $logger = Log::Log4perl->get_logger('pfsetvlan::handling'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); my $vlan_obj = new pf::vlan::custom(); # }}}2 # test if act on this trap {{{2 my $switch; $lockLogger->trace("locking - trying to lock switchFactory_locker in handleTrap"); { lock $switchFactory_locker; $lockLogger->trace("locking - obtained lock on switchFactory_locker in handleTrap"); $switch = $switchFactory->instantiate($switch_ip); } $lockLogger->trace("locking - unlocked switchFactory_locker in handleTrap"); if (!$switch) { $logger->error("Can not instantiate switch $switch_ip !"); cleanupAfterThread( $switch_ip, $switch_port ); return; } # do we actually act on this trap ? if ( defined($trapMac) && $switch->isFakeMac($trapMac) ) { $logger->info("MAC $trapMac is a fake MAC. Stop $trapType handling"); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } my $weActOnThisTrap = $vlan_obj->doWeActOnThisTrap($switch, $switch_port, $trapType); if ( $weActOnThisTrap == 0 ) { $logger->info("doWeActOnThisTrap returns false. Stop $trapType handling"); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}2 # trapType eq 'mac' {{{2 if ( $trapType eq 'mac' ) { # initialize {{{3 my $mac = lc($trapMac); my $vlan = $trapVlan; $logger->info("$trapOperation trap received on $switch_ip ifIndex $switch_port for $mac in VLAN $vlan"); # }}}3 # test if port is still in current VLAN {{{3 if ( $vlan ne $switch->getVlan($switch_port) ) { $logger->info( "$switch_ip ifIndex $switch_port is no longer in this VLAN -> Do nothing" ); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}3 # node_updatePF {{{3 my $isPhone = $switch->isPhoneAtIfIndex( $mac, $switch_port ); node_update_PF( $switch, $switch_port, $mac, $vlan, $isPhone, $switch->isRegistrationMode() ); # }}}3 # trapOperation eq 'removed' {{{3 if ( $trapOperation eq 'removed' ) { locationlog_update_end_mac($mac); #do nothing if it's a phone if ($isPhone) { $logger->info("MAC $mac is a VoIP phone -> Do nothing"); } else { #do we have an open entry in locationlog for switch/port ? my @locationlog = locationlog_view_open_switchport_no_VoIP( $switch_ip, $switch_port ); if ( (@locationlog) && ( scalar(@locationlog) > 0 ) && ( defined($locationlog[0]->{'mac'}) ) && ( $locationlog[0]->{'mac'} ne '' ) ) { if ($switch->isMacInAddressTableAtIfIndex( $mac, $switch_port ) ) { $logger->info( "Removed trap for MAC $mac: MAC " . $locationlog[0]->{'mac'} . " is still present in mac-address-table; has probably already been relearned -> DO NOTHING" ); } else { $logger->info( "Removed trap for MAC $mac: MAC " . $locationlog[0]->{'mac'} . " DEAD -> setting data VLAN on $switch_ip ifIndex $switch_port to MAC detection VLAN" ); # #Revised Section by Josh Fisk for stealth mode 11032011 # # $switch->setMacDetectionVlan( $switch_port, # \%switch_locker, 0 ); # # # # #Revised Section by Josh Fisk for stealth mode 11032011 # } } else { #no open entry in locationlog for switch/port $logger->info( "no line opened for MAC $mac in locationlog."); #try to determine if nothing is left on switch/port (VoIP phones dont' count) my $nothingLeftOnSwitchPort = 0; my @macArray = $switch->_getMacAtIfIndex($switch_port); if ( !@macArray ) { $nothingLeftOnSwitchPort = 1; } elsif ( scalar(@macArray) == 1 ) { my $onlyMacLeft = $macArray[0]; $logger->debug("only MAC found is $onlyMacLeft"); if ($switch->isPhoneAtIfIndex( $onlyMacLeft, $switch_port ) ) { $nothingLeftOnSwitchPort = 1; } } else { $logger->debug( scalar(@macArray) . " MACs found." ); } if ( $nothingLeftOnSwitchPort == 1 ) { $logger->info( # #Revised Section by Josh Fisk for stealth mode 11032011 # "setting data VLAN on $switch_ip ifIndex $switch_port to MAC detection VLAN" "Removing Active Location Log for MAC $mac on $switch_ip ifIndex $switch_port" ); # # # $switch->setMacDetectionVlan( $switch_port, # \%switch_locker, 0 ); # #Revised Section by Josh Fisk for stealth mode 11032011 # # } else { $logger->info( "no line in locationlog and MACs (" . join( ",", @macArray ) . ") still present on this port -> Do nothing" ); } } } # }}}3 # trapOperation eq 'learnt' {{{3 } elsif ( $trapOperation eq 'learnt' ) { # port security handling {{{4 do_port_security( $mac, $switch, $switch_port, $trapType ); # }}}4 #do nothing if it's a phone if ($isPhone) { $logger->info( "MAC $mac is a VoIP phone -> Do nothing besides updating locationlog" ); locationlog_synchronize($switch_ip, $switch_port, $switch->getVoiceVlan($switch_port), $mac, $VOIP, $WIRED_SNMP_TRAPS); } else { my $changeVlan = 0; #do we have an open entry in locationlog for switch/port ? my @locationlog = locationlog_view_open_switchport_no_VoIP( $switch_ip, $switch_port ); if ( (@locationlog) && ( scalar(@locationlog) > 0 ) && ( defined( $locationlog[0]->{'mac'} ) ) && ( $locationlog[0]->{'mac'} ne '' ) ) { if ( $locationlog[0]->{'mac'} =~ /^$mac$/i ) { if (( $locationlog[0]->{'vlan'} == $vlan ) && ($vlan == $vlan_obj->fetchVlanForNode($mac, $switch, $switch_port, $WIRED_SNMP_TRAPS)) ) { $logger->info( "locationlog is already up2date. Do nothing"); } else { $changeVlan = 1; } } else { $logger->info( "Learnt trap received for $mac. Old MAC " . $locationlog[0]->{'mac'} . " already connected to the port according to locationlog !" ); $changeVlan = 1; } } else { $changeVlan = 1; } if ( $changeVlan == 1 ) { # #Revised Section by Josh Fisk for stealth mode 11032011 # # node_determine_and_set_into_VLAN( $mac, $switch, $switch_port, $WIRED_SNMP_TRAPS ); # # locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS); # #Revised Section by Josh Fisk for stealth mode 11032011 # } } } # }}}2 # trapType eq 'secureMacAddrViolation' (with STATIC MACs) {{{2 } elsif ( $trapType eq 'secureMacAddrViolation' ) { $logger->info( "$trapType trap received on $switch_ip ifIndex $switch_port for $trapMac" ); # floating network devices handling {{{3 if (exists($ConfigFloatingDevices{$trapMac})) { $logger->info("The floating network device $trapMac has just plugged into $switch_ip port $switch_port. Enabling floating network device configuration on the port."); my $floatingDeviceManager = new pf::floatingdevice::custom(); my $result = $floatingDeviceManager->enablePortConfig($trapMac, $switch, $switch_port, \%switch_locker); if (! $result) { $logger->info("An error occured while enabling floating network device configuration on port $switch_port. It may not work!"); } cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # generic port security handling {{{3 my $secureMacAddrHashRef; if (do_port_security( lc($trapMac), $switch, $switch_port, $trapType ) eq 'stopTrapHandling' ) { $logger->info( "MAC $trapMac is already authorized on $switch_ip ifIndex $switch_port. Stopping secureMacAddrViolation trap handling here" ); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # node_update_PF {{{3 my $isPhone = $switch->isPhoneAtIfIndex( $trapMac, $switch_port ); node_update_PF( $switch, $switch_port, $trapMac, $trapVlan, $isPhone, $switch->isRegistrationMode() ); #}}}3 # synchronize locationlog with secure MAC addresses found on switchport {{{3 my $locationlog_phone = locationlog_view_open_switchport_only_VoIP( $switch_ip, $switch_port ); my @locationlog_pc = locationlog_view_open_switchport_no_VoIP( $switch_ip, $switch_port ); # }}}3 # close locationlog entries for MACs which are not present any more on the switch as secure MACs {{{3 $secureMacAddrHashRef = $switch->getSecureMacAddresses($switch_port); if (defined($locationlog_phone) && (!exists( $secureMacAddrHashRef->{ $locationlog_phone->{'mac'} } ) ) ) { # TODO: not so sure about this behavior $logger->debug( $locationlog_phone->{'mac'} . " (VoIP phone) has open locationlog entry at $switch_ip ifIndex $switch_port but is not a secure MAC address. Closing locationlog entry" ); locationlog_update_end_switchport_only_VoIP( $switch_ip, $switch_port ); $locationlog_phone = undef; } if ( (@locationlog_pc) && ( scalar(@locationlog_pc) > 0 ) && ( defined( $locationlog_pc[0]->{'mac'} ) ) && (!exists( $secureMacAddrHashRef->{ $locationlog_pc[0]->{'mac'} } ) ) ) { $logger->debug( $locationlog_pc[0]->{'mac'} . " has open locationlog entry at $switch_ip ifIndex $switch_port but is not a secure MAC address. Closing locationlog entry" ); locationlog_update_end_mac( $locationlog_pc[0]->{'mac'} ); @locationlog_pc = (); } # }}}3 # if trap came from a VoIP phone {{{3 if ($isPhone) { my $voiceVlan = $switch->getVoiceVlan($switch_port); $logger->debug("$trapType trap comes from VoIP $trapMac"); #is another VoIP phone authorized here ? if ( defined($locationlog_phone) ) { my $oldVoIPPhone = $locationlog_phone->{'mac'}; $logger->debug( "VoIP $oldVoIPPhone has still open locationlog entry at $switch_ip ifIndex $switch_port" ); if ( exists( $secureMacAddrHashRef->{$oldVoIPPhone} ) ) { $logger->info( "de-authorizing VoIP $oldVoIPPhone at old location $switch_ip ifIndex $switch_port VLAN $voiceVlan" ); $switch->authorizeMAC( $switch_port, $oldVoIPPhone, 0, $voiceVlan, 0 ); } $logger->debug( "closing VoIP $oldVoIPPhone locationlog entry at $switch_ip ifIndex $switch_port VLAN $voiceVlan" ); locationlog_update_end_switchport_only_VoIP( $switch_ip, $switch_port ); } #authorize MAC my $secureMacAddrHashRef = $switch->getSecureMacAddresses($switch_port); my $old_mac_to_remove = undef; foreach my $old_mac ( keys %$secureMacAddrHashRef ) { my $old_isPhone = $switch->isPhoneAtIfIndex( $old_mac, $switch_port ); if (( grep( { $_ == $voiceVlan } @{ $secureMacAddrHashRef->{$old_mac} } ) >= 1 ) || $old_isPhone ) { $old_mac_to_remove = $old_mac; } } if ( defined($old_mac_to_remove) ) { $logger->info( "authorizing VoIP $trapMac (old entry $old_mac_to_remove) at new location $switch_ip ifIndex $switch_port VLAN $voiceVlan" ); $switch->authorizeMAC( $switch_port, $old_mac_to_remove, $trapMac, $voiceVlan, $voiceVlan ); } else { $logger->info( "authorizing VoIP $trapMac at new location $switch_ip ifIndex $switch_port VLAN $voiceVlan" ); $switch->authorizeMAC( $switch_port, 0, $trapMac, 0, $voiceVlan ); } locationlog_synchronize($switch_ip, $switch_port, $voiceVlan, $trapMac, $VOIP, $WIRED_SNMP_TRAPS); # }}}3 # if trap came from a PC {{{3 } else { $logger->debug("$trapType trap comes from PC $trapMac"); if ( (@locationlog_pc) && ( defined( $locationlog_pc[0]->{'mac'} ) ) ) { my $oldPC = $locationlog_pc[0]->{'mac'}; $logger->debug("$oldPC has still open locationlog entry at $switch_ip ifIndex $switch_port. Closing it"); locationlog_update_end_mac($oldPC); $logger->info("authorizing $trapMac (old entry $oldPC) at new location $switch_ip ifIndex $switch_port"); my $correctVlanForThisNode = $vlan_obj->fetchVlanForNode( $trapMac, $switch, $switch_port, $WIRED_SNMP_TRAPS ); $switch->authorizeMAC( $switch_port, $oldPC, $trapMac, $switch->getVlan($switch_port), $correctVlanForThisNode ); #set the right VLAN $logger->debug("setting correct VLAN for $trapMac at new location $switch_ip ifIndex $switch_port"); $switch->setVlan( $switch_port, $correctVlanForThisNode, \%switch_locker, $trapMac ); } else { #authorize MAC my $secureMacAddrHashRef = $switch->getSecureMacAddresses($switch_port); my $voiceVlan = $switch->getVoiceVlan($switch_port); my $old_mac_to_remove = undef; foreach my $old_mac ( keys %$secureMacAddrHashRef ) { my $old_isPhone = $switch->isPhoneAtIfIndex( $old_mac, $switch_port ); if (( grep( { $_ == $voiceVlan } @{ $secureMacAddrHashRef->{$old_mac} } ) == 0 ) && ( !$old_isPhone ) ) { $old_mac_to_remove = $old_mac; } } my $correctVlanForThisNode = $vlan_obj->fetchVlanForNode( $trapMac, $switch, $switch_port, $WIRED_SNMP_TRAPS ); if ( defined($old_mac_to_remove) ) { $logger->info( "authorizing $trapMac (old entry $old_mac_to_remove) at new location $switch_ip ifIndex $switch_port" ); $switch->authorizeMAC( $switch_port, $old_mac_to_remove, $trapMac, $switch->getVlan($switch_port), $correctVlanForThisNode ); } else { $logger->info( "authorizing $trapMac at new location $switch_ip ifIndex $switch_port" ); $switch->authorizeMAC( $switch_port, 0, $trapMac, 0, $correctVlanForThisNode ); } #set the right VLAN $logger->debug( "setting correct VLAN for $trapMac at new location $switch_ip ifIndex $switch_port" ); $switch->setVlan( $switch_port, $correctVlanForThisNode, \%switch_locker, $trapMac ); } } # }}}3 # }}}2 # trapType eq 'secureDynamicMacAddrViolation' {{{2 } elsif ( $trapType eq 'secureDynamicMacAddrViolation' ) { $logger->warn( "ERROR: secureDynamicMacAddrViolation traps are currently not supported by PacketFence. Please check our website frequently regarding upgrades !" ); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; # }}}2 # trapType eq 'down' {{{2 } elsif ( $trapType eq 'down' ) { $logger->info("$trapType trap received on $switch_ip ifIndex $switch_port"); # continue only if security traps are not available on this port {{{3 if ( $switch->isPortSecurityEnabled($switch_port) ) { $logger->info("security traps are configured on this switch port. Stopping DOWN trap handling here"); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}3 # floating network devices handling {{{3 # if the last device pluggoed in that port is a floating network device then we handle it my @locationlog_switchport = locationlog_view_open_switchport_no_VoIP($switch_ip, $switch_port); my $valid_locationlog_entry = (@locationlog_switchport && ref($locationlog_switchport[0]) eq 'HASH'); if ($valid_locationlog_entry && (exists($ConfigFloatingDevices{$locationlog_switchport[0]->{mac}}))) { my $mac = $locationlog_switchport[0]->{mac}; $logger->info("The floating network device $mac has just unplugged from $switch_ip port $switch_port. " . "Disabling floating network device configuration on the port."); my $floatingDeviceManager = new pf::floatingdevice::custom(); my $result = $floatingDeviceManager->disablePortConfig($mac, $switch, $switch_port, \%switch_locker); if (!$result) { $logger->info("An error occured while disabling floating network device configuration on port " . "$switch_port. The port may not work!"); } cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}3 # set into MAC detection VLAN {{{3 # # #Revised Section by Josh Fisk for stealth mode 11032011 # $logger->info("Sealth Mode - Not setting $switch_ip port $switch_port to MAC detection VLAN"); # # $logger->info("setting $switch_ip port $switch_port to MAC detection VLAN"); # # $switch->setMacDetectionVlan( $switch_port, \%switch_locker, 1 ); # # # locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS); # #Revised Section by Josh Fisk for stealth mode 11032011 # # }}}3 # }}}2 # trapType eq 'up' {{{2 } elsif ( $trapType eq 'up' ) { $logger->info( "$trapType trap received on $switch_ip ifIndex $switch_port"); # continue only if security traps are not available on this port {{{3 if ( $switch->isPortSecurityEnabled($switch_port) ) { $logger->info( "security traps are configured on this switch port. Stopping UP trap handling here" ); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}3 # floating network devices handling {{{3 # if the last device pluggoed in that port is a floating network device then we handle it my @locationlog_switchport = locationlog_view_open_switchport_no_VoIP($switch_ip, $switch_port); my $valid_locationlog_entry = (@locationlog_switchport && ref($locationlog_switchport[0]) eq 'HASH'); if ($valid_locationlog_entry && (exists($ConfigFloatingDevices{$locationlog_switchport[0]->{mac}}))) { $logger->info("The logs shows that the last device pluged was a floating network device. We may have missed" . "the LinkDown trap. Disabling floating network device configuration on the port."); my $floatingDeviceManager = new pf::floatingdevice::custom(); # shut the port down $logger->debug("Shuting down port $switch_port"); if (! $switch->setAdminStatus( $switch_port, $SNMP::DOWN )) { $logger->error("An error occured while shuting down port $switch_port. The port may not work!"); } my $result = $floatingDeviceManager->disablePortConfig( $locationlog_switchport[0]->{mac}, $switch, $switch_port, \%switch_locker); if (!$result) { $logger->error("An error occured while disabling floating network device configuration on port " . " $switch_port. The port may not work!"); } # open the port $logger->debug("Re-opening port $switch_port"); if (! $switch->setAdminStatus( $switch_port, $SNMP::UP )) { $logger->error("An error occured while opening port $switch_port. The port may not work!"); } cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}3 # set into MAC detection VLAN {{{3 # # #Revised Section by Josh Fisk for stealth mode 11032011 # $logger->info("Sealth Mode - Not setting $switch_ip port $switch_port to MAC detection VLAN"); # # $logger->info("setting $switch_ip port $switch_port to MAC detection VLAN"); # # $switch->setMacDetectionVlan( $switch_port, \%switch_locker, 1 ); # # # locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS); # #Revised Section by Josh Fisk for stealth mode 11032011 # # }}}3 # continue only if MAC learnt traps are not available on this port {{{3 if ( $switch->isLearntTrapsEnabled($switch_port) ) { $logger->info( "MAC learnt traps are configured on this switch port. Stopping UP trap handling here" ); cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } # }}}3 # try to determine MAC address(es) {{{3 my $nbAttempts = 0; my $start = time; my @macArray = (); my $secureMacAddrHashRef; do { sleep( $switch->{_macSearchesSleepInterval} ) unless ( $nbAttempts == 0 ); my $mustRunCleanupAfterThread = 0; $lockLogger->trace( "locking - trying to lock threadLists_toBeKilled in handleTrap" ); { lock %threadList_toBeKilled; $lockLogger->trace( "locking - obtained lock on threadLists_toBeKilled in handleTrap" ); if (exists( $threadList_toBeKilled{"$switch_ip|$switch_port"} ) ) { $logger->info( "received kill signal for thread at $switch_ip ifIndex $switch_port" ); delete $threadList_toBeKilled{"$switch_ip|$switch_port"}; $mustRunCleanupAfterThread = 1; } } $lockLogger->trace( "locking - unlocked threadLists_toBeKilled in handleTrap"); if ( $mustRunCleanupAfterThread == 1 ) { cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); return; } $logger->debug( "attempt " . ($nbAttempts + 1) . " to obtain MAC at $switch_ip ifIndex $switch_port" ); @macArray = $switch->_getMacAtIfIndex($switch_port); $nbAttempts++; # TODO constantify the 120 seconds } while (($nbAttempts < $switch->{_macSearchesMaxNb}) && ((time-$start) < 120) && (scalar(@macArray) == 0)); if (scalar(@macArray) == 0) { if ($nbAttempts >= $switch->{_macSearchesMaxNb}) { $logger->warn("Tried to grab MAC address at ifIndex $switch_port " ."on switch ".$switch->{_ip}." ".$switch->{_macSearchesMaxNb}." times and failed"); } else { $logger->warn("Tried to grab MAC address at ifIndex $switch_port " ."on switch ".$switch->{_ip}." for 2 minutes and failed"); } } # }}}3 # node_update_PF {{{3 my @tmpMacArray = (); if ( scalar(@macArray) > 0 ) { #remove VoIP phones from list foreach my $currentMac (@macArray) { if ( $switch->isPhoneAtIfIndex( $currentMac, $switch_port ) ) { #this Mac is a phone $logger->debug("$currentMac is a phone"); node_update_PF( $switch, $switch_port, $currentMac, '', $TRUE, $switch->isRegistrationMode() ); } else { push( @tmpMacArray, $currentMac ); node_update_PF( $switch, $switch_port, $currentMac, '', $FALSE, $switch->isRegistrationMode() ); } } } @macArray = @tmpMacArray; # }}}3 # number of MACs found > 1 {{{3 if ( scalar(@macArray) > 1 ) { $logger->info("several MACs found. Do nothing"); # }}}3 # number of MACs found == 1 {{{3 } elsif ( scalar(@macArray) == 1 ) { my $mac = lc( $macArray[0] ); # port security handling {{{4 do_port_security( $mac, $switch, $switch_port, $trapType ); # }}}4 # node_determine_and_set_into_VLAN {{{4 # #Revised Section by Josh Fisk for stealth mode 11032011 # # node_determine_and_set_into_VLAN( $mac, $switch, $switch_port, $WIRED_SNMP_TRAPS ); # # locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS); # #Revised Section by Josh Fisk for stealth mode 11032011 # # }}}4 # }}}3 # number of MACs found == 0 {{{3 } else { $logger->info( "cannot find MAC (maybe we found a VoIP, but they don't count here). Do nothing" ); } # }}}3 # }}}2 # trapType eq 'desAssociate' {{{2 } elsif ( $trapType eq 'desAssociate' ) { my $connection_type = $trapOperation; # WARNING: I used trapOperation to carry the connection type $logger->info("$trapType trap received on $switch_ip for wireless client $trapMac"); #$switch->deauthenticateMac($trapMac); my $switchIp = $switch->{_ip}; if ($connection_type == $WIRELESS_802_1X) { # we spawn a shell to workaround a thread safety bug in Net::Appliance::Session when using SSH transport # http://www.cpanforum.com/threads/6909 pf_run("/usr/local/pf/bin/pfcmd_vlan -deauthenticateDot1x -switch $switchIp -mac $trapMac"); } else { # we spawn a shell to workaround a thread safety bug in Net::Appliance::Session when using SSH transport # http://www.cpanforum.com/threads/6909 pf_run("/usr/local/pf/bin/pfcmd_vlan -deauthenticate -switch $switchIp -mac $trapMac"); } # }}}2 # trapType eq 'reAssignVlan' {{{2 } elsif ( $trapType eq 'reAssignVlan' ) { my $connection_type = $trapOperation; # WARNING: I used trapOperation to carry the connection type $logger->info("$trapType trap received on $switch_ip ifIndex $switch_port"); if (defined($connection_type) && $connection_type == $WIRED_802_1X) { # we spawn a shell to workaround a thread safety bug in Net::Appliance::Session when using SSH transport # http://www.cpanforum.com/threads/6909 $logger->info("Forcing 802.1x re-authentication on $switch_ip:$switch_port. A new VLAN will be assigned."); pf_run("/usr/local/pf/bin/pfcmd_vlan -deauthenticateDot1x -switch $switch_ip -ifIndex $switch_port"); } elsif (defined($connection_type) && $connection_type == $WIRED_MAC_AUTH) { $switch->handleReAssignVlanTrapForWiredMacAuth($switch_port); } else { my @locationlog = locationlog_view_open_switchport_no_VoIP( $switch_ip, $switch_port ); if ((@locationlog) && ( scalar(@locationlog) > 0 ) && ( $locationlog[0]->{'mac'} ne '' )) { my $mac = $locationlog[0]->{'mac'}; if ( $switch->isPortSecurityEnabled($switch_port) ) { $logger->info( "security traps are configured on " . $switch->{_ip} . " ifIndex $switch_port. Re-assigning VLAN for $mac" ); my $hasPhone = $switch->hasPhoneAtIfIndex($switch_port); node_determine_and_set_into_VLAN( $mac, $switch, $switch_port, $connection_type ); # TODO extract that behavior in a method call in pf::vlan so it can be overridden easily if ( !$hasPhone ) { $logger->info( "no VoIP phone is currently connected at " . $switch->{_ip} . " ifIndex $switch_port. " . "Flipping port admin status" ); $switch->bouncePort( $switch_port ); } else { my @violations = violation_view_open_desc($mac); if ( scalar(@violations) > 0 ) { my %message; $message{'subject'} = "VLAN isolation of $mac behind VoIP phone"; $message{'message'} = "The following computer has been isolated behind a VoIP phone\n"; $message{'message'} .= "MAC: $mac\n"; my $node_info = node_view($mac); $message{'message'} .= "Owner: " . $node_info->{'pid'} . "\n"; $message{'message'} .= "Computer Name: " . $node_info->{'computername'} . "\n"; $message{'message'} .= "Notes: " . $node_info->{'notes'} . "\n"; $message{'message'} .= "Switch: " . $switch->{_ip} . "\n"; $message{'message'} .= "Port (ifIndex): " . $switch_port . "\n\n"; $message{'message'} .= "The violation details are\n"; foreach my $violation (@violations) { $message{'message'} .= "Description: " . $violation->{'description'} . "\n"; $message{'message'} .= "Start: " . $violation->{'start_date'} . "\n"; } $logger->info("sending email to admin regarding isolation of $mac behind VoIP phone"); pfmailer(%message); } } # #Revised Section by Josh Fisk for stealth mode 11032011 # # } else { # $logger->info( # "no security traps are configured on " . $switch->{_ip} . " ifIndex $switch_port. " . # "Flipping port admin status" # ); # $switch->bouncePort( $switch_port ); # } # } else { $logger->info( "Stealth Mode - no security traps are configured on " . $switch->{_ip} . " ifIndex $switch_port. " . "Flipping port admin status" ); } # # #Revised Section by Josh Fisk for stealth mode 11032011 # } else { $logger->warn( "received reAssignVlan trap on $switch_ip ifIndex $switch_port but can't determine non VoIP MAC" ); } } # }}}2 # trapType eq 'dot11Deauthentication' {{{2 } elsif ( $trapType eq 'dot11Deauthentication' ) { my $mac = $trapMac; $logger->info("$trapType trap received on $switch_ip for wireless client $mac. closing locationlog entry"); # we close the line opened for the mac. If there is no line, this won't do anything locationlog_update_end_mac( $mac ); # }}}2 # trapType eq 'desAssociate' {{{2 } elsif ( $trapType eq 'firewallRequest' ) { $logger->info("$trapType trap received for inline client: $trapMac. Modifying firewall."); # verify if firewall rule is ok my $inline = new pf::inline::custom(); $inline->performInlineEnforcement($trapMac); # }}}2 } # cleanupAfterThread {{{2 cleanupAfterThread( $switch_ip, $switch_port ); $switch->disconnectRead(); $switch->disconnectWrite(); # }}}2 } # }}}1 # sub cleanupAfterThread {{{1 sub cleanupAfterThread { my $logger = Log::Log4perl->get_logger('pfsetvlan::cleanup'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); my ( $switch_ip, $switch_port ) = @_; $lockLogger->trace( "locking - trying to lock threadList_running, threadList_toBeKilled in cleanupAfterThread" ); { lock %threadList_running; lock %threadList_toBeKilled; $lockLogger->trace( "locking - obtained lock on threadList_running, threadList_toBeKilled in cleanupAfterThread" ); delete $threadList_running{"$switch_ip|$switch_port"}; if ( exists( $threadList_toBeKilled{"$switch_ip|$switch_port"} ) ) { $logger->debug( "destroyed kill signal for thread at $switch_ip ifIndex $switch_port in cleanupAfterThread" ); delete $threadList_toBeKilled{"$switch_ip|$switch_port"}; } } $lockLogger->trace( "locking - unlocked threadList_running, threadList_toBeKilled in cleanupAfterThread" ); $lockLogger->trace( "locking - trying to lock threadList_queued in cleanupAfterThread"); { lock @threadList_queued; $lockLogger->trace( "locking - obtained lock on threadList_queued in cleanupAfterThread" ); $lockLogger->trace( "locking - sending signal for threadList_queued in cleanupAfterThread" ); cond_signal(@threadList_queued); } $lockLogger->trace( "locking - unlocked threadList_queued in cleanupAfterThread"); $logger->info("finished"); } # }}}1 # sub normal_sighandler {{{1 sub normal_sighandler { my $logger = Log::Log4perl->get_logger('pfsetvlan'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); if ( ( isenabled( $Config{'vlan'}{'closelocationlogonstop'} ) ) && ( threads->self->tid() == 0 ) ) { $logger->debug( "caught SIG" . $_[0] . " - closing open locationlogs" ); locationlog_close_all(); } $logger->logcroak("pfsetvlan: caught SIG" . $_[0] . " - terminating"); } # alarm signals are known to be buggy with threaded perl, to fix we create a handler that does nothing # ref: http://rt.perl.org/rt3/Public/Bug/Display.html?id=16807 # bug: http://www.packetfence.org/mantis/view.php?id=907 sub ignore_sighandler { my $logger = Log::Log4perl->get_logger('pfsetvlan'); $logger->error("caught SIG" . $_[0] . ". This is probably a telnet or ssh management attempt that timed out. " ."THIS WILL HANG A PACKETFENCE THREAD FOR SEVERAL MINUTES! Doublecheck your config or the network between " ."packetfence and the problematic device. Look for Can't connect messages in the logs to find the culprit."); } # }}}1 # sub node_update_PF {{{1 sub node_update_PF { my ($switch, $switch_port, $mac, $vlan, $isPhone, $registrationMode) = @_; my $logger = Log::Log4perl->get_logger('pfsetvlan'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); my $vlan_obj = new pf::vlan::custom(); #lowercase MAC $mac = lc($mac); if ( $switch->isFakeMac($mac) ) { $logger->info("MAC $mac is fake. Stopping node_update_PF"); return 0; } #add node if necessary if ( !node_exist($mac) ) { $logger->info( "node $mac does not yet exist in PF database. Adding it now"); node_add_simple($mac); } # There is activity from that mac, call node wakeup node_mac_wakeup($mac); #should we auto-register? if ($vlan_obj->shouldAutoRegister($mac, $registrationMode, 0, $isPhone, $WIRED_SNMP_TRAPS)) { # auto-register my %autoreg_node_defaults = $vlan_obj->getNodeInfoForAutoReg($switch->{_ip}, $switch_port, $mac, $vlan, $registrationMode, 0, $isPhone, $WIRED_SNMP_TRAPS); $logger->debug("auto-registering node $mac"); if (!node_register($mac, $autoreg_node_defaults{'pid'}, %autoreg_node_defaults)) { $logger->error("auto-registration of node $mac failed"); return 0; } } return 1; } # }}}1 # sub node_determine_and_set_into_VLAN {{{1 sub node_determine_and_set_into_VLAN { my ( $mac, $switch, $ifIndex, $connection_type ) = @_; my $logger = Log::Log4perl->get_logger('pfsetvlan::handling'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); my $vlan_obj = new pf::vlan::custom(); $switch->setVlan( $ifIndex, $vlan_obj->fetchVlanForNode($mac, $switch, $ifIndex, $connection_type), \%switch_locker, $mac ); } # }}}1 # sub do_port_security {{{1 sub do_port_security { my ( $mac, $switch, $switch_port, $trapType ) = @_; my $logger = Log::Log4perl->get_logger('pfsetvlan::handling'); my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking'); Log::Log4perl::MDC->put( 'tid', threads->self->tid() ); #determine if $mac is authorized elsewhere my $locationlog_mac = locationlog_view_open_mac($mac); if ( defined($locationlog_mac) && ( exists($switchFactory->{_config}{$locationlog_mac->{'switch'}}) ) ) { my $old_switch = $locationlog_mac->{'switch'}; my $old_port = $locationlog_mac->{'port'}; my $old_vlan = $locationlog_mac->{'vlan'}; my $is_old_voip = is_node_voip($mac); #we have to enter to 'if' always when trapType eq 'secureMacAddrViolation' if ( ( $old_switch ne $switch->{_ip} ) || ( $old_port != $switch_port ) || ( $trapType eq 'secureMacAddrViolation' ) ) { my $oldSwitch; $logger->debug( "$mac has still open locationlog entry at $old_switch ifIndex $old_port" ); if ( $old_switch eq $switch->{_ip} ) { $oldSwitch = $switch; } else { $lockLogger->trace("locking - trying to lock switchFactory_locker in $trapType handling"); { lock $switchFactory_locker; $lockLogger->trace("locking - obtained lock on switchFactory_locker in $trapType handling"); $oldSwitch = $switchFactory->instantiate($old_switch); } $lockLogger->trace("locking - unlocked switchFactory_locker in $trapType handling"); } if (!$oldSwitch) { $logger->error("Can not instantiate switch $old_switch !"); } else { $logger->info("Will try to check on this node's previous switch if secured entry needs to be removed. ". "Old Switch IP: $old_switch"); my $secureMacAddrHashRef = $oldSwitch->getSecureMacAddresses($old_port); if ( exists( $secureMacAddrHashRef->{$mac} ) ) { if ( ( $old_switch eq $switch->{_ip} ) && ( $old_port == $switch_port ) && ( $trapType eq 'secureMacAddrViolation' ) ) { return 'stopTrapHandling'; } my $fakeMac = $oldSwitch->generateFakeMac( $is_old_voip, $old_port ); $logger->info("de-authorizing $mac (new entry $fakeMac) at old location $old_switch ifIndex $old_port"); $oldSwitch->authorizeMAC( $old_port, $mac, $fakeMac, ( $is_old_voip ? $oldSwitch->getVoiceVlan($old_port) : $oldSwitch->getVlan($old_port) ), ( $is_old_voip ? $oldSwitch->getVoiceVlan($old_port) : $oldSwitch->getVlan($old_port) ) ); } else { $logger->info("MAC not found on node's previous switch secure table or switch inaccessible."); } locationlog_update_end_mac($mac); } } } # check if $mac is not already secured on another port (in case locationlog is outdated) my $secureMacAddrHashRef = $switch->getAllSecureMacAddresses(); if ( exists( $secureMacAddrHashRef->{$mac} ) ) { foreach my $ifIndex ( keys( %{ $secureMacAddrHashRef->{$mac} } ) ) { if ( $ifIndex == $switch_port ) { return 'stopTrapHandling'; } else { foreach my $vlan ( @{ $secureMacAddrHashRef->{$mac}->{$ifIndex} } ) { my $is_voice_vlan = ($vlan == $switch->getVoiceVlan($ifIndex)); my $fakeMac = $switch->generateFakeMac($is_voice_vlan, $ifIndex); $logger->info( "$mac is a secure MAC address at " . $switch->{_ip} . " ifIndex $ifIndex VLAN $vlan. De-authorizing (new entry $fakeMac)" ); $switch->authorizeMAC( $ifIndex, $mac, $fakeMac, $vlan, $vlan ); } } } } return 1; } # }}}1 END { if ( ( !$man ) && ( !$help ) ) { $logger->info("stopping pfsetvlan"); deletepid(); foreach my $t (@completeThreadList) { $t->detach; } kill 6, -$$; } } # vim: set shiftwidth=4: # vim: set expandtab: # vim: set backspace=indent,eol,start: # vim: set foldmethod=marker: # vim: set foldcolumn=4: | |||||||
![]() |
||||||
|
![]() |
|
(0002434) obilodeau (reporter) 2011-11-11 10:27 |
I haven't looked at the code but the concept you describe is definitely in the right direction with what the discovery mode should be. I'll review it and see where we can take it from there. Thanks for taking the time to push upstream! |
(0002437) packetfence4me (reporter) 2011-11-11 11:35 |
Ah, so after going through it again, it looks like the location (switch, port, VLAN) does show up but only under Node:Lookup. However the active location doesn't show up in the Node:View. I was hoping that under discovery mode the node locations show up in the Node:View so that I can export to a spreadsheet. I'm also seeing the trigger of violations while in discovery mode: my fault. But one thing that would be nice would be the option, in discovery mode, to ignore if the VLAN has been configured as a Managed VLAN. Thanks for your help |
(0002438) packetfence4me (reporter) 2011-11-11 15:41 |
I've been exploring the discovery mode option and I'm not sure if the locationlog functionality is fully there. There seems to be issues with updating the location from the snmp trap from the switch. The dhcplistener updates the node info great. But I'm have problems with the snmp trap updating the location. |
(0003722) lmunro (administrator) 2015-02-13 15:26 |
Old issues. Most are not relevant to PF 4 and up. Let's reopen the ones that matter when we move to github. |
![]() |
|||
Date Modified | Username | Field | Change |
2011-11-09 12:42 | packetfence4me | New Issue | |
2011-11-09 12:42 | packetfence4me | File Added: pfsetvlan | |
2011-11-11 10:27 | obilodeau | Note Added: 0002434 | |
2011-11-11 10:27 | obilodeau | Assigned To | => obilodeau |
2011-11-11 10:27 | obilodeau | Status | new => assigned |
2011-11-11 10:27 | obilodeau | Category | feature => core |
2011-11-11 10:27 | obilodeau | Target Version | => +1 |
2011-11-11 10:27 | obilodeau | Tag Attached: Code Review | |
2011-11-11 11:35 | packetfence4me | Note Added: 0002437 | |
2011-11-11 15:41 | packetfence4me | Note Added: 0002438 | |
2012-02-09 14:18 | obilodeau | Relationship added | related to 0001262 |
2015-02-13 15:26 | lmunro | Note Added: 0003722 | |
2015-02-13 15:26 | lmunro | Status | assigned => closed |
Copyright © 2000 - 2012 MantisBT Group |