1package Netdot::Model::PhysAddr;
2
3use base 'Netdot::Model';
4use warnings;
5use strict;
6
7my $logger = Netdot->log->get_logger('Netdot::Model::Device');
8
9=head1 NAME
10
11Netdot::Model::PhysAddr - Physical Address Class
12
13=head1 SYNOPSIS
14
15    my $valid = PhysAddr->validate($str);
16    my $p = PhysAddr->insert({address=>$address});
17    my $p = PhysAddr->search(address=>'DEADDEADBEEF')->first;
18
19=head1 CLASS METHODS
20=cut
21
22################################################################
23
24=head2 search - Search PhysAddr objects
25
26    Formats address before searching
27
28  Arguments:
29    Hash ref with PhysAddr fields
30  Returns:
31    PhysAddr object(s) or undef
32  Examples:
33    PhysAddr->search(address=>'DEADDEADBEEF');
34=cut
35
36sub search {
37    my ($class, @args) = @_;
38    $class->isa_class_method('search');
39
40    @args = %{ $args[0] } if ref $args[0] eq "HASH";
41    my $opts = @args % 2 ? pop @args : {};
42    my %argv = @args;
43
44    if ( $argv{address} ){
45	$argv{address} = $class->format_address($argv{address});
46    }
47    return $class->SUPER::search(%argv, $opts);
48}
49
50
51################################################################
52
53=head2 search_like - Search PhysAddr objects
54
55    Formats address before searching
56
57  Arguments:
58    Hash ref with PhysAddr fields
59  Returns:
60    PhysAddr object(s) or undef
61  Examples:
62    PhysAddr->search(address=>'DEADDEADBEEF');
63=cut
64
65sub search_like {
66    my ($self, %argv) = @_;
67
68    if ( $argv{address} ){
69	if ( $argv{address} =~ /^'(.*)'$/ ){
70	    # User wants exact match
71	    # do nothing
72	}else{
73	    $argv{address} = $self->format_address($argv{address});
74	}
75    }
76    return $self->SUPER::search_like(%argv);
77}
78
79
80################################################################
81
82=head2 insert - Insert PhysAddr object
83
84    We override the insert method for extra functionality
85
86  Arguments:
87    Hash ref with PhysAddr fields
88  Returns:
89    New PhysAddr object
90  Examples:
91    PhysAddr->insert(address=>'DEADDEADBEEF');
92=cut
93
94sub insert {
95    my ($self, $argv) = @_;
96
97    $argv->{first_seen} = $self->timestamp();
98    $argv->{last_seen}  = $self->timestamp();
99    $argv->{static}     = defined($argv->{static}) ?  $argv->{static} : 0;
100    return $self->SUPER::insert( $argv );
101}
102
103############################################################################
104
105=head2 retrieve_all_hashref - Build a hash with all addresses
106
107    Retrieves all macs from the DB
108    and stores them in a hash, indexed by address.
109    The value is the PhysAddr id
110
111  Arguments:
112    None
113  Returns:
114    Hash reference
115  Examples:
116    my $db_macs = PhysAddr->retriev_all_hashref();
117
118
119=cut
120
121sub retrieve_all_hashref {
122    my ($class) = @_;
123    $class->isa_class_method('retrieve_all_hashref');
124
125    # Build the search-all-macs SQL query
126    $logger->debug(sub{ "PhysAddr::retrieve_all_hashref: Retrieving all MACs..." });
127    my ($mac_aref, %db_macs, $sth);
128
129    my $dbh = $class->db_Main;
130    eval {
131	$sth = $dbh->prepare_cached("SELECT id,address FROM physaddr");
132	$sth->execute();
133	$mac_aref = $sth->fetchall_arrayref;
134    };
135    if ( my $e = $@ ){
136	$class->throw_fatal($e);
137    }
138    # Build a hash of mac addresses.
139    foreach my $row ( @$mac_aref ){
140	my ($id, $address) = @$row;
141	$db_macs{$address} = $id;
142    }
143    $logger->debug(sub{ "PhysAddr::retrieve_all_hashref: ...done" });
144
145    return \%db_macs;
146}
147
148##################################################################
149
150=head2 fast_update - Faster updates for specific cases
151
152    This method will traverse a list of hashes containing a MAC address
153    and a timestamp.  If a record does not exist with that address,
154    it is created and both timestamps ('first_seen' and 'last_seen') are
155    instantiated.
156    If the address already exists, only the 'last_seen' timestamp is
157    updated.
158
159    Meant to be used by processes that insert/update large amounts of
160    objects.  We use direct SQL commands for improved speed.
161
162  Arguments:
163    hash ref with key = MAC address string
164    timestamp
165  Returns:
166    True if successul
167  Examples:
168    PhysAddr->fast_update(\%macs);
169
170=cut
171
172sub fast_update {
173    my ($class, $macs, $timestamp) = @_;
174    $class->isa_class_method('fast_update');
175
176    my $start = time;
177    $logger->debug(sub{ "PhysAddr::fast_update: Updating MAC addresses in DB" });
178
179    my $dbh = $class->db_Main;
180    if ( $class->config->get('DB_TYPE') eq 'mysql' ){
181	# Take advantage of MySQL's "ON DUPLICATE KEY UPDATE"
182	my $sth = $dbh->prepare_cached("INSERT INTO physaddr (address,first_seen,last_seen,static)
183                                        VALUES (?, ?, ?, '0')
184                                        ON DUPLICATE KEY UPDATE last_seen=VALUES(last_seen);");
185	foreach my $address ( keys %$macs ){
186	    $sth->execute($address, $timestamp, $timestamp);
187	}
188    }else{
189	# Build SQL queries
190	my $sth1 = $dbh->prepare_cached("UPDATE physaddr SET last_seen=? WHERE address=?");
191
192	my $sth2 = $dbh->prepare_cached("INSERT INTO physaddr (address,first_seen,last_seen,static)
193                                         VALUES (?, ?, ?, '0')");
194
195	# Now walk our list
196	foreach my $address ( keys %$macs ){
197	    eval {
198		$sth2->execute($address, $timestamp, $timestamp);
199	    };
200	    if ( my $e = $@ ){
201		# Probably duplicate. That's OK. Update
202		eval {
203		    $sth1->execute($timestamp, $address);
204		};
205		if ( my $e2 = $@ ){
206		    # Something else is wrong
207		    $logger->error($e2);
208		}
209	    }
210	}
211    }
212
213    my $end = time;
214    $logger->debug(sub{ sprintf("PhysAddr::fast_update: Done Updating: %d addresses in %s",
215				scalar(keys %$macs), $class->sec2dhms($end-$start)) });
216
217    return 1;
218}
219
220################################################################
221
222=head2 validate - Format and validate MAC address strings
223
224    Assumes that "000000000000", "111111111111" ... "FFFFFFFFFF"
225    are invalid.  Also invalidates known bogus and Multicast addresses.
226
227  Arguments:
228    Physical address string
229  Returns:
230    Physical address string in canonical format, false if invalid
231  Examples:
232    my $validmac = PhysAddr->validate('DEADDEADBEEF');
233
234=cut
235
236sub validate {
237    my ($self, $addr) = @_;
238    $self->isa_class_method('validate');
239
240    return unless $addr;
241    $addr = $self->format_address($addr);
242    if ( $addr !~ /^[0-9A-F]{12}$/ ){
243	# Format must be DEADDEADBEEF
244	$logger->debug(sub{ "PhysAddr::validate: Bad format: $addr" });
245	return 0;
246
247    }elsif ( $addr =~ /^([0-9A-F]{1})/ && $addr =~ /$1{12}/ ) {
248	# Assume the all-equal-bits address is invalid
249	$logger->debug(sub{ "PhysAddr::validate: Bogus address: $addr" });
250	return 0;
251
252    }elsif ( $addr eq '000000000001' ) {
253	 # Skip Passport 8600 CLIP MAC addresses
254	$logger->debug(sub{ "PhysAddr::validate: CLIP: $addr" });
255	return 0;
256
257    }elsif ( $addr =~ /^00005E00/i ) {
258	 # Skip VRRP addresses
259	$logger->debug(sub{ "PhysAddr::validate: VRRP: $addr" });
260	return 0;
261
262    }elsif ( $addr =~ /^00000C07AC/i ) {
263	 # Skip VRRP addresses
264	$logger->debug(sub{ "PhysAddr::validate: HSRP: $addr" });
265	return 0;
266
267    }elsif ( $addr =~ /^([0-9A-F]{2})/ && $1 =~ /.(1|3|5|7|9|B|D|F)/ ) {
268	 # Multicast addresses
269	$logger->debug(sub{ "PhysAddr::validate: address is Multicast: $addr" });
270	return 0;
271
272    }elsif ( scalar(my @list = @{$self->config->get('IGNORE_MAC_PATTERNS')} ) ){
273	foreach my $ignored ( @list ){
274	    if ( $addr =~ /$ignored/i ){
275		$logger->debug(sub{"PhysAddr::validate: address matches configured pattern ($ignored): $addr"});
276		return 0;
277	    }
278	}
279    }
280
281    return $addr;
282}
283
284#################################################################
285
286=head2 from_interfaces - Get addresses that belong to interfaces
287
288  Arguments:
289    None
290  Returns:
291    Hash ref key=address, value=id
292  Examples:
293    my $intmacs = PhysAddr->from_interfaces();
294
295=cut
296
297sub from_interfaces {
298    my ($class) = @_;
299    $class->isa_class_method('from_interfaces');
300
301    # Build the SQL query
302    $logger->debug(sub{ "PhysAddr::from_interfaces: Retrieving all Interface MACs..." });
303    my ($mac_aref, %int_macs, $sth);
304
305    my $dbh = $class->db_Main;
306    eval {
307	$sth = $dbh->prepare_cached("SELECT p.id,p.address
308                                     FROM physaddr p, interface i
309                                     WHERE i.physaddr=p.id");
310	$sth->execute();
311	$mac_aref = $sth->fetchall_arrayref;
312    };
313    if ( my $e = $@ ){
314	$class->throw_fatal($e);
315    }
316    # Build a hash of mac addresses.
317    foreach my $row ( @$mac_aref ){
318	my ($id, $address) = @$row;
319	$int_macs{$address} = $id;
320    }
321    $logger->debug(sub{ "Physaddr::from_interfaces: ...done" });
322
323    return \%int_macs;
324
325}
326
327#################################################################
328
329=head2 map_all_to_ints - Map all MAC addresses to their interfaces
330
331  Arguments:
332    None
333  Returns:
334    Hash ref of hash refs
335     key => address
336     value => hashref with key => int id, value => mac id
337  Examples:
338    my $macs_to_ints = PhysAddr->map_all_to_ints();
339
340=cut
341
342sub map_all_to_ints {
343    my ($class) = @_;
344    $class->isa_class_method('map_all_to_ints');
345
346    # Build the SQL query
347    $logger->debug(sub{ "PhysAddr::map_all_to_ints: Retrieving all Interface MACs..." });
348
349    my $dbh = $class->db_Main;
350    my $sth = $dbh->prepare_cached("SELECT p.id, p.address, i.id
351                                      FROM physaddr p, interface i
352                                     WHERE i.physaddr=p.id");
353    $sth->execute();
354    my $mac_aref = $sth->fetchall_arrayref;
355
356    # Build the hash
357    my %map;
358    foreach my $row ( @$mac_aref ){
359	my ($mid, $address, $iid) = @$row;
360	$map{$address}{$iid} = $mid;
361    }
362    $logger->debug(sub{ "Physaddr::map_all_to_ints: ...done" });
363
364    return \%map;
365
366}
367
368#################################################################
369
370=head2 from_devices - Get addresses that are devices' base MACs
371
372  Arguments:
373    None
374  Returns:
375    Hash ref key=address, value=id
376  Examples:
377    my $intmacs = PhysAddr->from_devices();
378
379=cut
380
381sub from_devices {
382    my ($class) = @_;
383    $class->isa_class_method('from_devices');
384
385    # Build the SQL query
386    $logger->debug(sub{ "PhysAddr::from_devices: Retrieving all Device MACs..." });
387    my ($mac_aref, %dev_macs);
388
389    my $dbh = $class->db_Main;
390    eval {
391	my $sth = $dbh->prepare_cached("SELECT p.id,p.address
392                                        FROM   physaddr p, device d, asset a
393                                        WHERE  a.physaddr=p.id
394                                           AND d.asset_id=a.id");
395	$sth->execute();
396	$mac_aref = $sth->fetchall_arrayref;
397    };
398    if ( my $e = $@ ){
399	$class->throw_fatal($e);
400    }
401    # Build a hash of mac addresses.
402    foreach my $row ( @$mac_aref ){
403	my ($id, $address) = @$row;
404	$dev_macs{$address} = $id;
405    }
406    $logger->debug(sub{ "Physaddr::from_devices: ...done" });
407    return \%dev_macs;
408
409}
410
411#################################################################
412
413=head2 infrastructure - Get all infrastructure MACs
414
415  Arguments:
416    None
417  Returns:
418    Hash ref key=address, value=id
419  Examples:
420    my $intmacs = PhysAddr->infrastructure();
421
422=cut
423
424sub infrastructure {
425    my ($class) = @_;
426    $class->isa_class_method('infrastructure');
427
428    my $int_macs = $class->from_interfaces();
429    my $dev_macs = $class->from_devices();
430    my %inf_macs = %{$int_macs};
431    foreach my $address ( keys %$dev_macs ){
432	$inf_macs{$address} = $dev_macs->{$address};
433    }
434    return \%inf_macs;
435}
436
437################################################################
438
439=head2 vendor_count - Count MACs by vendor
440
441  Arguments:
442    type  -  [infrastructure|node|all]
443  Returns:
444    Array with:
445       - Hash ref keyed by oui, value is count
446       - Total of given type
447  Examples:
448    my %count = PhysAddr->vendor_count();
449
450=cut
451
452sub vendor_count{
453    my ($self, $type) = @_;
454    $self->isa_class_method('vendor_count');
455    $type ||= 'all';
456    my (%res, $macs);
457    if ( $type eq 'infrastructure' ){
458	$macs = $self->infrastructure();
459    }elsif ( $type eq 'node' ){
460	my $infra = $self->infrastructure();
461	my $all   = $self->retrieve_all_hashref();
462	foreach my $address ( keys %$all ){
463	    if ( !exists $infra->{$address} ){
464		$macs->{$address} = $all->{$address};
465	    }
466	}
467    }elsif ( $type eq 'all' ){
468	$macs = $self->retrieve_all_hashref();
469    }
470    my $total = 0;
471    my $OUI = OUI->retrieve_all_hashref;
472    foreach my $address ( keys %$macs ){
473	my $oui = $self->_oui_from_address($address);
474	my $vendor = $OUI->{$oui} || "Unknown";
475	$res{$oui}{vendor} = $vendor;
476	$res{$oui}{total}++;
477	$total++;
478    }
479    return (\%res, $total);
480}
481
482################################################################
483
484=head2 - is_broad_multi - Check for broadcast/multicast bit
485
486    IEEE 802.3 specifies that the lowest order bit in the first
487    octet designates an address as broadcast/multicast.
488
489  Arguments:
490    As an object method, none.
491    As a class method, the address is required.
492  Returns:
493    True or false
494  Examples:
495     PhysAddr->is_broad_multi($address)
496    or
497     $physaddr->is_broad_multi();
498=cut
499
500sub is_broad_multi {
501    my ($class, $address) = @_;
502    my $self;
503    if ( $self = ref($class) ){
504	$address = $self->address;
505    }else{
506	$class->throw_fatal("PhysAddr::is_broad_multi: Need an address to continue")
507	    unless $address;
508    }
509    my $dec = hex(substr($address, 1, 1));
510    return 1 if ( $dec & 1 );
511    return 0
512}
513
514=head1 INSTANCE METHODS
515=cut
516
517################################################################
518
519=head2 colon_address - Return address with octets separated by colons
520
521    This can be either an instance method or class method
522
523  Arguments:
524    None if called as instance method
525    address string if called as class method
526  Returns:
527    String (e.g. 'DE:AD:DE:AD:BE:EF')
528  Examples:
529    print $physaddr->colon_address;
530    print PhysAddr->colon_address('DEADDEADBEEF');
531
532=cut
533
534sub colon_address {
535    my ($self, $address) = @_;
536    my $class = ref($self);
537    my $addr;
538    if ( $class ){
539	$addr = $self->address;
540    }else{
541	$addr = $address ||
542	    $self->throw_fatal("PhysAddr::colon_address: Missing address string");
543    }
544    my @octets  = unpack("A2" x 6, $addr);
545    return join ':', @octets;
546}
547
548################################################################
549
550=head2 oui - Return Organizationally Unique Identifier for a given PhysAddr object
551
552  Arguments:
553    None
554  Returns:
555    String (e.g. '00022F')
556  Examples:
557    print $physaddr->oui;
558
559=cut
560
561sub oui {
562    my ($self) = @_;
563    $self->isa_object_method('oui');
564    return $self->_oui_from_address($self->address);
565}
566
567################################################################
568
569=head2 vendor - Return OUI vendor name
570
571  Arguments:
572    None if called as an object method
573    MAC Address if called as class method
574  Returns:
575    String (e.g. 'Cisco Systems')
576  Examples:
577    print $physaddr->vendor;
578    print PhysAddr->vendor('DEADEADBEEF');
579
580=cut
581
582sub vendor {
583    my ($self, $address) = @_;
584    my $class = ref($self) || $self;
585    my $ouistr;
586    if ( ref($self) ){
587	# Being called as an object method
588	$ouistr = $self->oui;
589    }else{
590	$class->throw_fatal("PhysAddr::vendor: Missing address")
591	    unless ( defined $address );
592	$ouistr =  $class->_oui_from_address($address);
593    }
594    return $self->_get_vendor_from_oui($ouistr);
595}
596
597################################################################
598
599=head2 find_edge_port - Find edge port where this MAC is located
600
601    The idea is to get all device ports whose latest forwarding
602    tables included this address.
603    If we get more than one, select the one whose forwarding
604    table had the least entries.
605
606  Arguments:
607    None
608  Returns:
609    Interface id
610  Examples:
611    print $physaddr->find_edge_port;
612
613=cut
614
615sub find_edge_port {
616    my ($self) = @_;
617    $self->isa_object_method('find_edge_port');
618    my ($sth, $sth2, $rows, $rows2);
619    my $dbh = $self->db_Main();
620    $sth = $dbh->prepare_cached('SELECT   DISTINCT(i.id), ft.id
621                                 FROM     interface i, fwtableentry fte, fwtable ft
622                                 WHERE    fte.physaddr=?
623                                   AND    fte.interface=i.id
624                                   AND    fte.fwtable=ft.id
625                                   AND    ft.tstamp=?
626                                   AND    i.neighbor IS NULL');
627
628	$sth->execute($self->id, $self->last_seen);
629	$rows = $sth->fetchall_arrayref;
630
631    if ( scalar @$rows > 1 ){
632	my @results;
633	$sth2 = $dbh->prepare_cached('SELECT COUNT(i.id)
634                                      FROM   interface i, fwtable ft, fwtableentry fte
635                                      WHERE  fte.fwtable=ft.id
636                                        AND  fte.interface=i.id
637                                        AND  ft.id=?
638                                        AND  fte.interface=?');
639
640	foreach my $row ( @$rows ){
641	    my ($iid, $ftid) = @$row;
642	    $sth2->execute($ftid, $iid);
643	    $rows2 = $sth2->fetchall_arrayref;
644
645	    foreach my $row2 ( @$rows2 ){
646		my ($count) = @$row2;
647		push @results, [$count, $iid];
648	    }
649	}
650	@results = sort { $a->[0] <=> $b->[0] } @results;
651	my $result = $results[0];
652	return $result->[1];
653    }else{
654	return $rows->[0]->[0];
655    }
656}
657
658
659################################################################
660
661=head2 get_last_n_fte - Get last N forwarding table entries
662
663  Arguments:
664    limit  - Return N last entries (default: 10)
665  Returns:
666    Array ref of timestamps and Interface IDs
667  Examples:
668    print $physaddr->get_last_n_fte(10);
669
670=cut
671
672sub get_last_n_fte {
673    my ($self, $limit) = @_;
674    $self->isa_object_method('get_last_n_fte');
675    my $id = $self->id;
676    my $dbh = $self->db_Main();
677    my $q1 = "SELECT   ft.tstamp
678              FROM     physaddr p, interface i, fwtableentry fte, fwtable ft
679              WHERE    p.id=$id
680                AND    fte.physaddr=p.id
681                AND    fte.interface=i.id
682                AND    fte.fwtable=ft.id
683              GROUP BY ft.tstamp
684              ORDER BY ft.tstamp DESC
685              LIMIT $limit";
686
687    my @tstamps = @{ $dbh->selectall_arrayref($q1) };
688    return unless @tstamps;
689    my $tstamps = join ',', map { "'$_'" } map { $_->[0] } @tstamps;
690
691    my $q2 = "SELECT   ft.tstamp, i.id
692              FROM     physaddr p, interface i, fwtableentry fte, fwtable ft
693              WHERE    p.id=$id
694                AND    fte.physaddr=p.id
695                AND    fte.interface=i.id
696                AND    fte.fwtable=ft.id
697                AND    ft.tstamp IN($tstamps)
698              ORDER BY ft.tstamp DESC";
699
700    return $dbh->selectall_arrayref($q2);
701}
702
703################################################################
704
705=head2 get_last_n_arp - Get last N ARP entries
706
707  Arguments:
708    limit  - Return N last entries (default: 10)
709  Returns:
710    Array ref of timestamps and Interface IDs
711  Examples:
712    print $physaddr->get_last_n_arp(10);
713
714=cut
715
716sub get_last_n_arp {
717    my ($self, $limit) = @_;
718    $self->isa_object_method('get_last_n_arp');
719
720    my $dbh = $self->db_Main();
721    my $id = $self->id;
722    my $q1 = "SELECT   arp.tstamp
723              FROM     physaddr p, interface i, arpcacheentry arpe, arpcache arp, ipblock ip
724              WHERE    p.id=$id AND arpe.physaddr=p.id AND arpe.interface=i.id
725                AND    arpe.ipaddr=ip.id AND arpe.arpcache=arp.id
726              GROUP BY arp.tstamp
727              ORDER BY arp.tstamp DESC
728              LIMIT $limit";
729
730    my @tstamps = @{ $dbh->selectall_arrayref($q1) };
731    return unless @tstamps;
732    my $tstamps = join ',', map { "'$_'" } map { $_->[0] } @tstamps;
733
734    my $q2 = "SELECT   i.id, ip.id, arp.tstamp
735              FROM     physaddr p, interface i, arpcacheentry arpe, arpcache arp, ipblock ip
736              WHERE    p.id=$id
737                AND    arpe.physaddr=p.id
738                AND    arpe.interface=i.id
739                AND    arpe.ipaddr=ip.id
740                AND    arpe.arpcache=arp.id
741                AND    arp.tstamp IN($tstamps)
742              ORDER BY arp.tstamp DESC";
743
744    return $dbh->selectall_arrayref($q2);
745}
746
747################################################################
748
749=head2 devices - Return devices whose base mac is this one
750
751  Arguments: None
752  Returns:   Array of Device objects
753  Examples:
754    my @devs = $physaddr->devices;
755
756=cut
757
758sub devices {
759    my ($self) = @_;
760    map { $_->devices } $self->assets;
761}
762
763################################################################
764
765=head2 search_interface_macs
766
767  Arguments:
768    Interface id
769    Timestamp
770  Returns:
771    Array of PhysAddr objects
772  Examples:
773    my @macs = PhysAddr->search_interface_macs($iid, $tstamp)
774
775=cut
776
777__PACKAGE__->set_sql(interface_macs => qq{
778SELECT p.id
779FROM     physaddr p, interface i, fwtable ft, fwtableentry fte
780WHERE    fte.interface=i.id
781  AND    fte.fwtable=ft.id
782  AND    fte.physaddr=p.id
783  AND    i.id=?
784  AND    ft.tstamp=?
785ORDER BY p.address    });
786
787#################################################
788# Add some triggers
789
790__PACKAGE__->add_trigger( deflate_for_create => \&_canonicalize );
791__PACKAGE__->add_trigger( deflate_for_update => \&_canonicalize );
792
793
794#################################################
795# PRIVATE METHODS
796#################################################
797
798#################################################
799# _canonicalize - Convert MAC address to canonical form
800#
801#    - Formats address
802#    - Calls validate function
803#
804#  Arguments:
805#    physical address object
806#  Returns:
807#    True if successful
808#
809sub _canonicalize {
810    my $self = shift;
811    my $class = ref($self) || $self;
812    my $address = ($self->_attrs('address'))[0];
813    $self->throw_user("Missing address")
814	unless $address;
815    $address = $self->format_address($address);
816    unless ( $class->validate( $address ) ){
817	$self->throw_user("Invalid Address: $address");
818    }
819    $self->_attribute_store( address => $address );
820}
821
822
823#################################################
824
825=head2 format_address
826
827    Format MAC address
828    - Removes usual separators
829    - Converts to all uppercase
830
831=cut
832
833sub format_address {
834    my ($self, $address) = @_;
835    $self->throw_user("Missing address")
836	unless $address;
837    $address =~ s/[:\-\.]//g;
838    $address = uc($address);
839    return $address;
840}
841
842################################################################
843# Extract first 6 characters from MAC address
844#
845sub _oui_from_address {
846    my ($self, $addr) = @_;
847    return substr($addr, 0, 6);
848}
849
850################################################################
851# Get Vendor information given an OUI string
852sub _get_vendor_from_oui {
853    my ($class, $ouistr) = @_;
854    my $oui = OUI->search(oui=>$ouistr)->first;
855    return $oui->vendor if defined $oui;
856    return "Unknown";
857}
858
859
860=head1 AUTHOR
861
862Carlos Vicente, C<< <cvicente at ns.uoregon.edu> >>
863
864=head1 COPYRIGHT & LICENSE
865
866Copyright 2012 University of Oregon, all rights reserved.
867
868This program is free software; you can redistribute it and/or modify
869it under the terms of the GNU General Public License as published by
870the Free Software Foundation; either version 2 of the License, or
871(at your option) any later version.
872
873This program is distributed in the hope that it will be useful, but
874WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
875or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
876License for more details.
877
878You should have received a copy of the GNU General Public License
879along with this program; if not, write to the Free Software Foundation,
880Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
881
882=cut
883
884#Be sure to return 1
8851;
886