File Coverage

blib/lib/Net/SNMP/Mixin/ArubaCX/VlanStatic.pm
Criterion Covered Total %
statement 81 152 53.2
branch 27 72 37.5
condition 3 6 50.0
subroutine 19 21 90.4
pod 3 3 100.0
total 133 254 52.3


line stmt bran cond sub pod time code
1             package Net::SNMP::Mixin::ArubaCX::VlanStatic;
2              
3 4     4   403966 use strict;
  4         12  
  4         126  
4 4     4   23 use warnings;
  4         9  
  4         149  
5              
6             #
7             # store this package name in a handy variable,
8             # used for unambiguous prefix of mixin attributes
9             # storage in object hash
10             #
11             my $prefix = __PACKAGE__;
12              
13             #
14             # this module import config
15             #
16 4     4   21 use Carp ();
  4         9  
  4         141  
17 4     4   987 use Net::SNMP ();
  4         80894  
  4         129  
18 4     4   691 use Net::SNMP::Mixin::Util qw/idx2val hex2octet push_error get_init_slot/;
  4         15431  
  4         33  
19             #
20             # this module export config
21             #
22             my @mixin_methods;
23              
24             BEGIN {
25 4     4   2346 @mixin_methods = (
26             qw/
27             map_vlan_id2name
28             map_if_idx2vlan_id
29             map_vlan_id2if_idx
30             /
31             );
32             }
33              
34 4         37 use Sub::Exporter -setup => {
35             exports => [@mixin_methods],
36             groups => { default => [@mixin_methods], },
37 4     4   33 };
  4         11  
38              
39             #
40             # SNMP oid constants used in this module
41             #
42             use constant {
43              
44             # BRIDGE-MIB
45             ############
46              
47 4         2907 DOT1D_BASE_PORT_IF_INDEX => '1.3.6.1.2.1.17.1.4.1.2',
48              
49             # IEEE8021-Q-BRIDGE-MIB
50             #######################
51              
52             #from IEEE8021_Q_BRIDGE_VLAN_STATIC_TABLE => '1.3.111.2.802.1.1.4.1.4.3',
53              
54             IEEE8021_Q_BRIDGE_VLAN_STATIC_NAME => '1.3.111.2.802.1.1.4.1.4.3.1.3',
55             IEEE8021_Q_BRIDGE_VLAN_STATIC_EGRESSPORTS => '1.3.111.2.802.1.1.4.1.4.3.1.4',
56             IEEE8021_Q_BRIDGE_VLAN_STATIC_ROW_STATUS => '1.3.111.2.802.1.1.4.1.4.3.1.7',
57              
58             # IEEE8021_Q_BRIDGE_VLAN_STATIC_UNTAGGED_PORTS => '1.3.111.2.802.1.1.4.1.4.3.1.6'
59              
60             # BUG until at least PL.10.08.0001
61             # VLAN_STATIC_UNTAGGED_PORTS is identical to IEEE8021_Q_BRIDGE_VLAN_STATIC_EGRESSPORTS
62              
63             # Gimmick: use instead the Pvid (untagged) info from
64             # from IEEE8021_Q_BRIDGE_PORT_VLAN_TABLE => '.1.3.111.2.802.1.1.4.1.4.5',
65             IEEE8021_Q_BRIDGE_PVID => '.1.3.111.2.802.1.1.4.1.4.5.1.1',
66 4     4   2186 };
  4         12  
67              
68             =head1 NAME
69              
70             Net::SNMP::Mixin::ArubaCX::VlanStatic - mixin class for ArubaCX static vlan info
71              
72             =cut
73              
74             our $VERSION = '0.02';
75              
76             =head1 SYNOPSIS
77              
78             use Net::SNMP;
79             use Net::SNMP::Mixin qw/mixer init_mixins/;
80              
81             my $session = Net::SNMP->session( -hostname => 'foo.bar.com');
82             $session->mixer('Net::SNMP::Mixin::ArubaCX::VlanStatic');
83             $session->init_mixins;
84             snmp_dispatcher() if $session->nonblocking;
85             $session->init_ok();
86             die $session->errors if $session->errors;
87              
88             my $vlan_id2name = $session->map_vlan_id2name();
89             foreach my $vlan_id ( keys %{$vlan_id2name} ) {
90             printf "Vlan-Id: %4d => Vlan-Name: %s\n",
91             $vlan_id, $vlan_id2name->{$vlan_id};
92             }
93              
94             my $vlan_ids2if_idx = $session->map_vlan_id2if_idx();
95             foreach my $id ( keys %{$vlan_ids2if_idx} ) {
96             printf "Vlan-Id: %4d\n", $id;
97             printf "\tTagged-Ports: %s\n", ( join ',', @{ $vlan_ids2if_idx->{$id}{tagged} } );
98             printf "\tUntagged-Ports: %s\n", ( join ',', @{ $vlan_ids2if_idx->{$id}{untagged} } );
99             }
100              
101             # sorted by interface
102             my $ports2ids = $session->map_if_idx2vlan_id();
103             foreach my $if_idx ( keys %{$ports2ids} ) {
104             printf "Interface: %10d\n", $if_idx;
105             printf "\tTagged-Vlans: %s\n", ( join ',', @{ $ports2ids->{$if_idx}{tagged} } );
106             printf "\tUntagged-Vlans: %s\n", ( join ',', @{ $ports2ids->{$if_idx}{untagged} } );
107             }
108              
109             =head1 DESCRIPTION
110              
111             A mixin class for vlan related infos from the IEEE8021-Q-BRIDGE-MIB used by ArubaCX.
112              
113             The mixin-module provides methods for mapping between vlan-ids and vlan-names und relations between interface indexes and vlan-ids,
114             tagged or untagged on these interfaces.
115              
116             =head1 MIXIN METHODS
117              
118             =head2 B<< OBJ->map_vlan_id2name() >>
119              
120             Returns a hash reference with statically configured vlan-ids as keys and the corresponing vlan-names as values:
121              
122             {
123             vlan_id => vlan_name,
124             vlan_id => vlan_name,
125             ... ,
126             }
127              
128             =cut
129              
130             sub map_vlan_id2name {
131 1     1 1 31863 my $session = shift;
132 1         6 my $agent = $session->hostname;
133              
134 1 50       12 Carp::croak "$agent: '$prefix' not initialized,"
135             unless $session->init_ok($prefix);
136              
137 0         0 my @active_vlan_ids = @{ $session->{$prefix}{activeVlanIds} };
  0         0  
138              
139 0         0 my $result = {};
140 0         0 foreach my $vlan_id (@active_vlan_ids) {
141              
142 0         0 $result->{$vlan_id} = $session->{$prefix}{VlanStaticNames}{$vlan_id};
143             }
144              
145 0         0 return $result;
146             }
147              
148             =head2 B<< OBJ->map_vlan_id2if_idx() >>
149              
150             Returns a hash reference with the vlan-ids as keys and tagged and untagged if_idx as values:
151              
152             {
153             vlan_id => {
154             tagged => [if_idx, ..., ],
155             untagged => [if_idx, ..., ],
156             },
157              
158             ... ,
159             }
160            
161             =cut
162              
163             sub map_vlan_id2if_idx {
164 1     1 1 759 my $session = shift;
165 1         13 my $agent = $session->hostname;
166              
167 1 50       8 Carp::croak "$agent: '$prefix' not initialized,"
168             unless $session->init_ok($prefix);
169              
170 0         0 my @active_vlan_ids = @{ $session->{$prefix}{activeVlanIds} };
  0         0  
171 0         0 my $bridge_port2if_idx = $session->{$prefix}{dot1dBasePortIfIndex};
172              
173 0         0 my $result;
174              
175             # loop over all active vlan ids
176 0         0 foreach my $vlan_id (@active_vlan_ids) {
177              
178             # tagged/untagged ports for this vlan_id
179 0         0 my @tagged_ports;
180             my @untagged_ports;
181              
182             # loop over all possible bridge-ports
183 0         0 foreach my $bridge_port ( sort { $a <=> $b } keys %$bridge_port2if_idx ) {
  0         0  
184 0         0 my $if_idx = $bridge_port2if_idx->{$bridge_port};
185              
186 0 0       0 push @tagged_ports, $if_idx
187             if _is_tagged( $session, $bridge_port, $vlan_id );
188              
189 0 0       0 push @untagged_ports, $if_idx
190             if _is_untagged( $session, $bridge_port, $vlan_id );
191             }
192              
193 0         0 $result->{$vlan_id} = { tagged => \@tagged_ports, untagged => \@untagged_ports };
194             }
195 0         0 return $result;
196             }
197              
198             =head2 B<< OBJ->map_if_idx2vlan_id() >>
199              
200             Returns a hash reference with the interfaces as keys and tagged and untagged vlan-ids as values:
201              
202             {
203             if_idx => {
204             tagged => [vlan_id, ..., ],
205             untagged => [vlan_id, ..., ],
206             },
207              
208             ... ,
209             }
210            
211             =cut
212              
213             sub map_if_idx2vlan_id {
214 1     1 1 824 my $session = shift;
215 1         5 my $agent = $session->hostname;
216              
217 1 50       8 Carp::croak "$agent: '$prefix' not initialized,"
218             unless $session->init_ok($prefix);
219              
220 0         0 my @active_vlan_ids = @{ $session->{$prefix}{activeVlanIds} };
  0         0  
221 0         0 my $bridge_port2if_idx = $session->{$prefix}{dot1dBasePortIfIndex};
222              
223 0         0 my $result = {};
224              
225             # loop over all possible bridge-ports
226 0         0 foreach my $bridge_port ( sort { $a <=> $b } keys %$bridge_port2if_idx ) {
  0         0  
227              
228 0         0 my @tagged_vlans;
229             my @untagged_vlans;
230              
231             # loop over all active vlans
232 0         0 foreach my $vlan_id (@active_vlan_ids) {
233              
234 0 0       0 push @tagged_vlans, $vlan_id
235             if _is_tagged( $session, $bridge_port, $vlan_id );
236              
237 0 0       0 push @untagged_vlans, $vlan_id
238             if _is_untagged( $session, $bridge_port, $vlan_id );
239             }
240              
241 0         0 my $if_idx = $bridge_port2if_idx->{$bridge_port};
242 0         0 $result->{$if_idx} = { tagged => \@tagged_vlans, untagged => \@untagged_vlans };
243             }
244 0         0 return $result;
245             }
246              
247             =head1 INITIALIZATION
248              
249             =head2 B<< OBJ->_init($reload) >>
250              
251             Fetch basic Dot1Q Vlan related snmp values from the host. Don't call this method direct!
252              
253             =cut
254              
255             #
256             # due to the asynchron nature, we don't know what init job is really the last, we decrement
257             # the value after each callback
258             #
259 4     4   35 use constant THIS_INIT_JOBS => 3;
  4         19  
  4         5333  
260              
261             sub _init {
262 4     4   11561 my ( $session, $reload ) = @_;
263 4         16 my $agent = $session->hostname;
264              
265             die "$agent: $prefix already initialized and reload not forced.\n"
266             if exists get_init_slot($session)->{$prefix}
267 4 50 66     31 && get_init_slot($session)->{$prefix} == 0
      33        
268             && not $reload;
269              
270             # set number of async init jobs for proper initialization
271 4         153 get_init_slot($session)->{$prefix} = THIS_INIT_JOBS;
272              
273             # bridge ports to ifIndex mapping
274 4         52 _fetch_dot1d_base_ports($session);
275 4 100       34 return if $session->error;
276              
277             # initialize the object for current vlan tag infos
278 2         19 _fetch_ieee8021q_vlan_static_tbl_entries($session);
279 2 50       26 return if $session->error;
280              
281             # initialize the object for pvid (untag) infos
282 2         17 _fetch_ieee8021q_port_vlan_tbl_entries($session);
283 2 50       13 return if $session->error;
284              
285 2         20 return 1;
286             }
287              
288             =head1 PRIVATE METHODS
289              
290             Only for developers or maintainers.
291              
292             =cut
293              
294             =head2 B<< _fetch_dot1d_base_ports() >>
295              
296             Fetch the mapping between brigePort and ifIndex
297              
298             =cut
299              
300             sub _fetch_dot1d_base_ports {
301 4     4   11 my $session = shift;
302 4         9 my $result;
303              
304             # fetch the dot1dBasePorts, in blocking or nonblocking mode
305 4 100       37 $result = $session->get_entries(
306             -columns => [ DOT1D_BASE_PORT_IF_INDEX, ],
307              
308             # define callback if in nonblocking mode
309             $session->nonblocking ? ( -callback => \&_dot1d_base_ports_cb ) : (),
310             );
311              
312 4 100       2013423 unless ( defined $result ) {
313 2 50       23 if ( my $err_msg = $session->error ) {
314 2         54 push_error( $session, "$prefix: $err_msg" );
315             }
316 2         124 return;
317             }
318              
319             # in nonblocking mode the callback will be called asynchronously
320 2 50       10 return 1 if $session->nonblocking;
321              
322             # call the callback function in blocking mode by hand
323 0         0 _dot1d_base_ports_cb($session);
324              
325             }
326              
327             =head2 B<< _dot1d_base_ports_cb($session) >>
328              
329             The callback for _fetch_dot1d_base_ports.
330              
331             =cut
332              
333             sub _dot1d_base_ports_cb {
334 2     2   2005520 my $session = shift;
335 2         8 my $vbl = $session->var_bind_list;
336              
337 2 50       21 unless ( defined $vbl ) {
338 2 50       11 if ( my $err_msg = $session->error ) {
339 2         35 push_error( $session, "$prefix: $err_msg" );
340             }
341 2         80 return;
342             }
343              
344             # mangle result table to get plain idx->value
345              
346 0         0 $session->{$prefix}{dot1dBasePortIfIndex} = idx2val( $vbl, DOT1D_BASE_PORT_IF_INDEX );
347              
348             # this init job is finished
349 0         0 get_init_slot($session)->{$prefix}--;
350              
351 0         0 return 1;
352             }
353              
354             =head2 B<< _fetch_ieee8021q_vlan_static_tbl_entries() >>
355              
356             Fetch the vlan tag info for current vlans.
357              
358             =cut
359              
360             sub _fetch_ieee8021q_vlan_static_tbl_entries {
361 2     2   5 my $session = shift;
362 2         4 my $result;
363              
364             # fetch the vlan tag info from ieee8021qVlanStaticTable
365 2 50       11 $result = $session->get_entries(
    50          
366             -columns => [
367             IEEE8021_Q_BRIDGE_VLAN_STATIC_NAME,
368             IEEE8021_Q_BRIDGE_VLAN_STATIC_EGRESSPORTS,
369             IEEE8021_Q_BRIDGE_VLAN_STATIC_ROW_STATUS,
370             ],
371              
372             # define callback if in nonblocking mode
373             $session->nonblocking ? ( -callback => \&_ieee8021q_vlan_static_tbl_entries_cb ) : (),
374              
375             # dangerous for snmp version 2c and 3, big values
376             # snmp-error: Message size exceeded buffer maxMsgSize
377             #
378             $session->version ? ( -maxrepetitions => 3 ) : (),
379             );
380              
381 2 50       2647 unless ( defined $result ) {
382 0 0       0 if ( my $err_msg = $session->error ) {
383 0         0 push_error( $session, "$prefix: $err_msg" );
384             }
385 0         0 return;
386             }
387              
388             # in nonblocking mode the callback will be called asynchronously
389 2 50       21 return 1 if $session->nonblocking;
390              
391             # call the callback function in blocking mode by hand
392 0         0 _ieee8021q_vlan_static_tbl_entries_cb($session);
393              
394             }
395              
396             =head2 B<< _ieee8021q_vlan_static_tbl_entries_cb($session) >>
397              
398             The callback for _fetch_ieee8021q_vlan_static_tbl_entries_cb.
399              
400             =cut
401              
402             sub _ieee8021q_vlan_static_tbl_entries_cb {
403 2     2   839 my $session = shift;
404 2         7 my $vbl = $session->var_bind_list;
405              
406 2 50       21 unless ( defined $vbl ) {
407 2 50       9 if ( my $err_msg = $session->error ) {
408 2         20 push_error( $session, "$prefix: $err_msg" );
409             }
410 2         55 return;
411             }
412              
413             #----------------------------------------------------------------------
414             # ieee8021QBridgeVlanStaticEntry OBJECT-TYPE
415             # SYNTAX Ieee8021QBridgeVlanStaticEntry
416             # MAX-ACCESS not-accessible
417             # STATUS current
418             # DESCRIPTION
419             # "Static information for a VLAN configured into the device by (local or network) management."
420             # INDEX { ieee8021QBridgeVlanStaticComponentId, ieee8021QBridgeVlanStaticVlanIndex }
421             # ::= { ieee8021QBridgeVlanStaticTable 1 }
422             #----------------------------------------------------------------------
423             #
424             # cut off the index ieee8021QBridgeVlanStaticComponentId with idx2val( $vbl, base_oid, 1, undef )
425             #
426             # mangle result table to get plain
427             # VlanId => value
428             #
429 0         0 $session->{$prefix}{VlanStaticNames} = idx2val( $vbl, IEEE8021_Q_BRIDGE_VLAN_STATIC_NAME, 1, undef );
430 0         0 $session->{$prefix}{VlanStaticEgressPorts} = idx2val( $vbl, IEEE8021_Q_BRIDGE_VLAN_STATIC_EGRESSPORTS, 1, undef );
431 0         0 $session->{$prefix}{VlanStaticRowStatus} = idx2val( $vbl, IEEE8021_Q_BRIDGE_VLAN_STATIC_ROW_STATUS, 1, undef );
432              
433             $session->{$prefix}{activeVlanIds} = [
434 0         0 grep { $session->{$prefix}{VlanStaticRowStatus}{$_} == 1 }
435 0         0 keys %{ $session->{$prefix}{VlanStaticRowStatus} }
  0         0  
436             ];
437              
438 0         0 foreach my $vlan ( @{ $session->{$prefix}{activeVlanIds} } ) {
  0         0  
439 0         0 my $egress_ports = $session->{$prefix}{VlanStaticEgressPorts}{$vlan};
440              
441             # It's importend, that the returned SNMP OCTET_STRINGs
442             # were untranslated by Net::SNMP!
443             # if already translated, we must reconvert it to a pure OCTET-STRING.
444              
445 0         0 $egress_ports = hex2octet($egress_ports);
446              
447 0         0 $session->{$prefix}{EgressPorts}{$vlan} = unpack( 'B*', $egress_ports );
448             }
449              
450             # this init job is finished
451 0         0 get_init_slot($session)->{$prefix}--;
452              
453 0         0 return 1;
454             }
455              
456             =head2 B<< _fetch_ieee8021q_port_vlan_tbl_entries() >>
457              
458             Fetch the pvid (untag) info for bridge ports.
459              
460             =cut
461              
462             sub _fetch_ieee8021q_port_vlan_tbl_entries {
463 2     2   6 my $session = shift;
464 2         6 my $result;
465              
466             # fetch the pvid untag info from ieee8021qPortVlanTable
467 2 50       10 $result = $session->get_entries( -columns => [ IEEE8021_Q_BRIDGE_PVID, ],
468              
469             # define callback if in nonblocking mode
470             $session->nonblocking ? ( -callback => \&_port_pvids_cb ) : (),
471             );
472              
473 2 50       2024 unless ( defined $result ) {
474 0 0       0 if ( my $err_msg = $session->error ) {
475 0         0 push_error( $session, "$prefix: $err_msg" );
476             }
477 0         0 return;
478             }
479              
480             # in nonblocking mode the callback will be called asynchronously
481 2 50       8 return 1 if $session->nonblocking;
482              
483             # call the callback function in blocking mode by hand
484 0         0 _port_pvids_cb($session);
485              
486             }
487              
488             =head2 B<< _port_pvids_cb($session) >>
489              
490             The callback for _fetch_ieee8021q_port_vlan_tbl_entries.
491              
492             =cut
493              
494             sub _port_pvids_cb {
495 2     2   788 my $session = shift;
496 2         10 my $vbl = $session->var_bind_list;
497              
498 2 50       21 unless ( defined $vbl ) {
499 2 50       7 if ( my $err_msg = $session->error ) {
500 2         17 push_error( $session, "$prefix: $err_msg" );
501             }
502 2         46 return;
503             }
504              
505             #---------------------------------------------------------------------------------------------------
506             # ieee8021QBridgePortVlanEntry OBJECT-TYPE
507             # SYNTAX Ieee8021QBridgePortVlanEntry
508             # MAX-ACCESS not-accessible
509             # STATUS current
510             # DESCRIPTION
511             # "Information controlling VLAN configuration for a port
512             # on the device. This is indexed by ieee8021BridgeBasePort."
513             # AUGMENTS { ieee8021BridgeBasePortEntry }
514             # ::= { ieee8021QBridgePortVlanTable 1 }
515             #---------------------------------------------------------------------------------------------------
516              
517             # AUGMENTS ieee8021BridgeBasePortEntry
518              
519             #---------------------------------------------------------------------------------------------------
520             # ieee8021BridgeBasePortEntry OBJECT-TYPE
521             # SYNTAX Ieee8021BridgeBasePortEntry
522             # MAX-ACCESS not-accessible
523             # STATUS current
524              
525             # DESCRIPTION
526             # "A list of objects containing information for each port
527             # of the Bridge."
528             # INDEX { ieee8021BridgeBasePortComponentId, ieee8021BridgeBasePort }
529             # ::= { ieee8021BridgeBasePortTable 1 }
530             #---------------------------------------------------------------------------------------------------
531             #
532             # cut off the index ieee8021BridgeBasePortComponentId with idx2val( $vbl, base_oid, 1, undef )
533             #
534             # mangle result table to get plain
535             # Port => VlanId
536             #
537              
538 0           $session->{$prefix}{Pvid} = idx2val( $vbl, IEEE8021_Q_BRIDGE_PVID, 1, undef );
539              
540             # this init job is finished
541 0           get_init_slot($session)->{$prefix}--;
542              
543 0           return 1;
544             }
545              
546             # returns true if $vlan_id is tagged on $bridge_port
547             sub _is_tagged {
548 0     0     my ( $session, $bridge_port, $vlan_id ) = @_;
549              
550 0 0         die "missing attribute 'bridge_port'" unless defined $bridge_port;
551 0 0         die "missing attribute 'vlan_id'" unless defined $vlan_id;
552              
553             # it's a bitstring,
554             # substr() counts from 0, bridge_ports from 1
555 0           my $egressed = substr( $session->{$prefix}{EgressPorts}{$vlan_id}, $bridge_port - 1, 1 );
556              
557             # VLAN is not egressed on this port -> false
558 0 0         return unless $egressed;
559              
560             # VLAN is PVID (untagged) on this port -> false
561 0 0         return if _is_untagged( $session, $bridge_port, $vlan_id );
562              
563 0           return 1;
564             }
565              
566             # returns true if $vlan_id is pvid on $bridge_port
567             sub _is_untagged {
568 0     0     my ( $session, $bridge_port, $vlan_id ) = @_;
569              
570 0 0         die "missing attribute 'bridge_port'" unless defined $bridge_port;
571 0 0         die "missing attribute 'vlan_id'" unless defined $vlan_id;
572              
573 0           return $session->{$prefix}{Pvid}{$bridge_port} == $vlan_id;
574             }
575              
576             =head1 REQUIREMENTS
577              
578             L<< Net::SNMP >>, L<< Net::SNMP::Mixin >>
579              
580             =head1 AUTHOR
581              
582             Johannes Deger
583             Karl Gaissmaier
584              
585             =head1 COPYRIGHT & LICENSE
586              
587             Copyright 2021 Karl Gaissmaier, all rights reserved.
588              
589             This program is free software; you can redistribute it and/or modify it
590             under the same terms as Perl itself.
591              
592             =cut
593              
594             unless ( caller() ) {
595             print "$prefix compiles and initializes successful.\n";
596             }
597              
598             1;
599              
600             # vim: sw=2