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