1package App::Netdisco::Util::Node; 2 3use Dancer qw/:syntax :script/; 4use Dancer::Plugin::DBIC 'schema'; 5 6use NetAddr::MAC; 7use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; 8 9use base 'Exporter'; 10our @EXPORT = (); 11our @EXPORT_OK = qw/ 12 check_mac 13 is_nbtstatable 14 store_arp 15/; 16our %EXPORT_TAGS = (all => \@EXPORT_OK); 17 18=head1 NAME 19 20App::Netdisco::Util::Node 21 22=head1 DESCRIPTION 23 24A set of helper subroutines to support parts of the Netdisco application. 25 26There are no default exports, however the C<:all> tag will export all 27subroutines. 28 29=head1 EXPORT_OK 30 31=head2 check_mac( $node, $device?, $port_macs? ) 32 33Given a MAC address, perform various sanity checks which need to be done 34before writing an ARP/Neighbor entry to the database storage. 35 36Returns false, and might log a debug level message, if the checks fail. 37 38Returns a true value (the MAC address in IEEE format) if these checks pass: 39 40=over 4 41 42=item * 43 44MAC address is well-formed (according to common formats) 45 46=item * 47 48MAC address is not all-zero, broadcast, CLIP, VRRP or HSRP 49 50=back 51 52Optionally pass a Device instance or IP to use in logging. 53 54Optionally pass a cached set of Device port MAC addresses as the third 55argument, in which case an additional check is added: 56 57=over 4 58 59=item * 60 61MAC address does not belong to an interface on any known Device 62 63=back 64 65=cut 66 67sub check_mac { 68 my ($node, $device, $port_macs) = @_; 69 return 0 if !$node; 70 71 my $mac = NetAddr::MAC->new(mac => ($node || '')); 72 my $devip = ($device ? (ref $device ? $device->ip : $device) : ''); 73 $port_macs ||= {}; 74 75 # incomplete MAC addresses (BayRS frame relay DLCI, etc) 76 if (!defined $mac or $mac->errstr) { 77 debug sprintf ' [%s] check_mac - mac [%s] malformed - skipping', 78 $devip, $node; 79 return 0; 80 } 81 else { 82 # lower case, hex, colon delimited, 8-bit groups 83 $node = lc $mac->as_ieee; 84 } 85 86 # broadcast MAC addresses 87 return 0 if $mac->is_broadcast; 88 89 # all-zero MAC addresses 90 return 0 if $node eq '00:00:00:00:00:00'; 91 92 # CLIP 93 return 0 if $node eq '00:00:00:00:00:01'; 94 95 # multicast 96 if ($mac->is_multicast and not $mac->is_msnlb) { 97 debug sprintf ' [%s] check_mac - multicast mac [%s] - skipping', 98 $devip, $node; 99 return 0; 100 } 101 102 # VRRP 103 if ($mac->is_vrrp) { 104 debug sprintf ' [%s] check_mac - VRRP mac [%s] - skipping', 105 $devip, $node; 106 return 0; 107 } 108 109 # HSRP 110 if ($mac->is_hsrp or $mac->is_hsrp2) { 111 debug sprintf ' [%s] check_mac - HSRP mac [%s] - skipping', 112 $devip, $node; 113 return 0; 114 } 115 116 # device's own MACs 117 if ($port_macs and exists $port_macs->{$node}) { 118 debug sprintf ' [%s] check_mac - mac [%s] is device port - skipping', 119 $devip, $node; 120 return 0; 121 } 122 123 return $node; 124} 125 126=head2 is_nbtstatable( $ip ) 127 128Given an IP address, returns C<true> if Netdisco on this host is permitted by 129the local configuration to nbtstat the node. 130 131The configuration items C<nbtstat_no> and C<nbtstat_only> are checked 132against the given IP. 133 134Returns false if the host is not permitted to nbtstat the target node. 135 136=cut 137 138sub is_nbtstatable { 139 my $ip = shift; 140 141 return if check_acl_no($ip, 'nbtstat_no'); 142 143 return unless check_acl_only($ip, 'nbtstat_only'); 144 145 return 1; 146} 147 148=head2 store_arp( \%host, $now? ) 149 150Stores a new entry to the C<node_ip> table with the given MAC, IP (v4 or v6) 151and DNS host name. Host details are provided in a Hash ref: 152 153 { 154 ip => '192.0.2.1', 155 node => '00:11:22:33:44:55', 156 dns => 'myhost.example.com', 157 } 158 159The C<dns> entry is optional. The update will mark old entries for this IP as 160no longer C<active>. 161 162Optionally a literal string can be passed in the second argument for the 163C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used. 164 165=cut 166 167sub store_arp { 168 my ($hash_ref, $now) = @_; 169 $now ||= 'now()'; 170 my $ip = $hash_ref->{'ip'}; 171 my $mac = NetAddr::MAC->new(mac => ($hash_ref->{'node'} || '')); 172 my $name = $hash_ref->{'dns'}; 173 174 return if !defined $mac or $mac->errstr; 175 176 schema('netdisco')->txn_do(sub { 177 my $current = schema('netdisco')->resultset('NodeIp') 178 ->search( 179 { ip => $ip, -bool => 'active'}, 180 { columns => [qw/mac ip/] })->update({active => \'false'}); 181 182 schema('netdisco')->resultset('NodeIp') 183 ->update_or_create( 184 { 185 mac => $mac->as_ieee, 186 ip => $ip, 187 dns => $name, 188 active => \'true', 189 time_last => \$now, 190 }, 191 { 192 key => 'primary', 193 for => 'update', 194 }); 195 }); 196} 197 1981; 199