package xCAT::SLP; use Carp; use IO::Select; use strict; use xCAT::Utils; my $ip6support = eval { require IO::Socket::INET6; require Socket6; 1; }; use Socket; unless ($ip6support) { require IO::Socket::INET; } #TODO: somehow get at system headers to get the value, put in linux's for now use constant IPV6_MULTICAST_IF => 17; use constant IP_MULTICAST_IF => 32; use constant REQ_INTERVAL => 1; my %xid_to_srvtype_map; my $xid; my $gprlist; my %searchmacs; my %ip4neigh; my %ip6neigh; my %servicehash; my %sendhash; my $attrpy = 0; my $serrpy = 0; sub getmulticasthash { my $hash = 0; my @nums = unpack("C*", shift); foreach my $num (@nums) { $hash *= 33; $hash += $num; $hash &= 0xffff; } $hash &= 0x3ff; $hash |= 0x1000; return sprintf("%04x", $hash); } sub dodiscover { my %args = @_; my $unicast = $args{unicast}; #should be used with -s ! my $ipranges = $args{range}; my $rspcount = 0; my $rspcount1 = 0; my $sendcount = 1; $xid = int(rand(16384)) + 1; my %rethash; unless ($args{'socket'}) { if ($ip6support) { $args{'socket'} = IO::Socket::INET6->new(Proto => 'udp'); } else { $args{'socket'} = IO::Socket::INET->new(Proto => 'udp'); } #make an extra effort to request biggest receive buffer OS is willing to give us if (-r "/proc/sys/net/core/rmem_max") { # we can detect the maximum allowed socket, read it. my $sysctl; open($sysctl, "<", "/proc/sys/net/core/rmem_max"); my $maxrcvbuf = <$sysctl>; my $rcvbuf = $args{'socket'}->sockopt(SO_RCVBUF); if ($maxrcvbuf > $rcvbuf) { $args{'socket'}->sockopt(SO_RCVBUF, $maxrcvbuf / 2); } } } #end of unless socket unless ($args{SrvTypes}) { croak "SrvTypes argument is required for xCAT::SLP::Dodiscover"; } unless (xCAT::Utils->isAIX()) { # AIX bug, can't set socket with SO_BROADCAST, otherwise multicast can't work. setsockopt($args{'socket'}, SOL_SOCKET, SO_BROADCAST, 1); #allow for broadcasts to be sent, we know what we are doing } my @srvtypes; if (ref $args{SrvTypes}) { @srvtypes = @{ $args{SrvTypes} }; } else { @srvtypes = split /,/, $args{SrvTypes}; } my $interfaces = get_interfaces(%args); if ($args{Ip}) { foreach my $nic (keys %$interfaces) { if (${ ${ $interfaces->{$nic} }{ipv4addrs} }[0] =~ /(\d+\.\d+\.\d+\.\d+)/) { unless ($args{Ip} =~ $1) { delete $interfaces->{$nic}; } } } } my @printip; foreach my $iface (keys %{$interfaces}) { foreach my $sip (@{ $interfaces->{$iface}->{ipv4addrs} }) { my $ip = $sip; $ip =~ s/\/(.*)//; push @printip, $ip; } } my $printinfo = join(",", @printip); if ($unicast) { if (xCAT::Utils->isAIX()) { send_message($args{reqcallback}, 1, "lsslp unicast is not supported on AIX"); exit 1; } if (!-f "/usr/bin/nmap") { send_message($args{reqcallback}, 1, "nmap does not exist, lsslp unicast is not possible"); exit 1; } my @servernodes; my @iprange = split /,/, $ipranges; foreach my $range (@iprange) { send_message($args{reqcallback}, 0, "Processing range $range..."); if ($range =~ /\/(\d+)/) { if ($1 < 16) { send_message($args{reqcallback}, 0, "The rarge is too large and may be time consuming. Broadcast is recommended."); } } #no need to check site.nmapoptions because it specifilly # uses T5 for certain performance requirement. `/usr/bin/nmap $range -sn -PE -n --send-ip -T5 `; my $nmapres = `/usr/bin/nmap $range -PE -p 427 -n --send-ip -T5 `; foreach my $line (split(/\n\n/, $nmapres)) { my $server; foreach my $sline (split(/\n/, $line)) { #output of nmap command for version under 5.10 if ($sline =~ /Interesting ports on (\d+\.\d+\.\d+\.\d+)/) { $server = $1; } #output of nmap command for version 5.10 and above if ($sline =~ /Nmap scan report for (\d+\.\d+\.\d+\.\d+)/) { $server = $1; } if ($sline =~ /427/ and ($sline =~ /open/ or $sline =~ /filtered/)) { push @servernodes, $server; } } # end of foreach line } # end of foreach line } # end of foreach pi-range unless (@servernodes) { send_message($args{reqcallback}, 0, "Nmap returns nothing"); return undef; } my $number = scalar(@servernodes); send_message($args{reqcallback}, 0, "Begin to do unicast to $number nodes..."); my %rechash; pipe CREAD, PWRITE; my $pid = xCAT::Utils->xfork(); if (!defined($pid)) { send_message($args{reqcallback}, 1, "Fork error: $!"); return undef; } elsif ($pid == 0) { close PWRITE; foreach my $srvtype (@srvtypes) { my $packet = generate_attribute_request(%args, SrvType => $srvtype); foreach my $destserver (@servernodes) { my $destip = inet_aton($destserver); my $destaddr = sockaddr_in(427, $destip); my $res = $args{'socket'}->send($packet, 0, $destaddr); } # end of foreach destserver } # end of foreach services while () { chomp; my $destserver = $_; if ($destserver =~ /NowYouNeedToDie/) { close CREAD; exit 0; } foreach my $srvtype (@srvtypes) { my $packet = generate_attribute_request(%args, SrvType => $srvtype); my $destip = inet_aton($destserver); my $destaddr = sockaddr_in(427, $destip); for (my $j = 0 ; $j < 1 ; $j++) { my $res = $args{'socket'}->send($packet, 0, $destaddr); } # end of foreach j++ } # end of foreach services } # end of while (cread) } else { close CREAD; $rspcount = 0; my $waittime = ($args{Time} > 0) ? $args{Time} : 300; my $deadline = time() + $waittime; my $waitforsocket = IO::Select->new(); $waitforsocket->add($args{'socket'}); my $rectime = time() + 5; my $recvzero = 0; while ($deadline > time()) { $rspcount1 = 0; while ($rectime > time()) { while ($waitforsocket->can_read(0)) { my $slppacket; my $peer = $args{'socket'}->recv($slppacket, 3000, 0); $rechash{$peer} = $slppacket; } #end of can_read } # end of receiving # now begin to parse the packets for my $tp (keys %rechash) { my @restserver; my $pkg = $tp; my $slpkg = $rechash{$tp}; my ($port, $flow, $ip6n, $ip4n, $scope); my $peername; if ($ip6support) { ($port, $flow, $ip6n, $scope) = Socket6::unpack_sockaddr_in6_all($pkg); $peername = Socket6::inet_ntop(Socket6::AF_INET6(), $ip6n); } else { ($port, $ip4n) = sockaddr_in($pkg); $peername = inet_ntoa($ip4n); } if ($peername =~ /\./) { #ipv4 $peername =~ s/::ffff://; } if ($rethash{$peername}) { next; #got a dupe, discard } my $result = process_slp_packet(packet => $slpkg, sockaddr => $pkg, 'socket' => $args{'socket'}, peername => $peername, callback => $args{reqcallback}); if ($result) { $rspcount++; $rspcount1++; $result->{peername} = $peername; $result->{scopeid} = $scope; $result->{sockaddr} = $pkg; my $hashkey; if ($peername =~ /fe80/) { $peername .= '%' . $scope; } $rethash{$peername} = $result; if ($args{Callback}) { $args{Callback}->($result); } foreach my $mynode (@servernodes) { unless ($mynode =~ $peername) { push @restserver, $mynode; } #end of mynode=~peername } # end of foreach @servernodes = @restserver; } # end of if result } # end of foreach processing foreach my $node (@servernodes) { syswrite PWRITE, "$node\n"; } # end of foreach servernodes $recvzero++ unless ($rspcount1); last if ($recvzero > 2); } # end of while(deadline) syswrite PWRITE, "NowYouNeedToDie\n"; close PWRITE; if (@servernodes) { my $miss = join(",", @servernodes); send_message($args{reqcallback}, 0, "Warning: can't get attributes from these nodes' replies: $miss. Please re-send unicast to these nodes.") if ($args{reqcallback}); } } # end of parent process } else { send_message($args{reqcallback}, 0, "Sending SLP request on interfaces: $printinfo ...") if ($args{reqcallback} and !$args{nomsg}); foreach my $srvtype (@srvtypes) { send_service_request_single(%args, ifacemap => $interfaces, SrvType => $srvtype); } unless ($args{NoWait}) { #in nowait, caller owns the responsibility.. #by default, report all respondants within 3 seconds: my $waitforsocket = IO::Select->new(); $waitforsocket->add($args{'socket'}); my $retrytime = ($args{Retry} > 0) ? $args{Retry} + 1 : 3; my $retryinterval = ($args{Retry} > 0) ? $args{Retry} : REQ_INTERVAL; my $waittime = ($args{Time} > 0) ? $args{Time} : 20; my @peerarray; my @pkgarray; my $startinterval = time(); my $interval; my $deadline = time() + $waittime; my ($port, $flow, $ip6n, $ip4n, $scope); my $slppacket; my $peername; while ($deadline > time()) { ######################################## # receive untill there is none ######################################## while ($waitforsocket->can_read(0)) { my $peer = $args{'socket'}->recv($slppacket, 3000, 0); push @peerarray, $peer; push @pkgarray, $slppacket; } ####################################### # process the packets ####################################### for (my $j = 0 ; $j < scalar(@peerarray) ; $j++) { my $pkg = $peerarray[$j]; my $slpkg = $pkgarray[$j]; if ($ip6support) { ($port, $flow, $ip6n, $scope) = Socket6::unpack_sockaddr_in6_all($pkg); $peername = Socket6::inet_ntop(Socket6::AF_INET6(), $ip6n); } else { ($port, $ip4n) = sockaddr_in($pkg); $peername = inet_ntoa($ip4n); } if ($rethash{$peername}) { next; #got a dupe, discard } my $result = process_slp_packet(packet => $slpkg, sockaddr => $pkg, 'socket' => $args{'socket'}, peername => $peername, callback => $args{reqcallback}); if ($result) { if ($peername =~ /\./) { #ipv4 $peername =~ s/::ffff://; } $result->{peername} = $peername; if ($gprlist) { $gprlist .= ',' . $peername if (length($gprlist) < 1250); } else { $gprlist = $peername; } $result->{scopeid} = $scope; $result->{sockaddr} = $pkg; my $hashkey; if ($peername =~ /fe80/) { $peername .= '%' . $scope; } $rspcount++; $rspcount1++; $rethash{$peername} = $result; if ($args{Callback}) { $args{Callback}->($result); } } } ############################# # check if need to return ############################# @peerarray = (); @pkgarray = (); $interval = time() - $startinterval; if ($args{Time} and $args{Count}) { if ($rspcount >= $args{Count} or $interval >= $args{Time}) { send_message($args{reqcallback}, 0, "Received $rspcount1 responses.") if ($args{reqcallback} and !$args{nomsg}); last; } } if ($sendcount > $retrytime and $rspcount1 == 0) { send_message($args{reqcallback}, 0, "Received $rspcount1 responses.") if ($args{reqcallback} and !$args{nomsg}); last; } ######################### # send request again ######################### if ($interval > $retryinterval) { #* (2**$sendcount))) { #double time $sendcount++; $startinterval = time(); send_message($args{reqcallback}, 0, "Received $rspcount1 responses.") if ($args{reqcallback} and !$args{nomsg}); send_message($args{reqcallback}, 0, "Sending SLP request on interfaces: $printinfo ...") if ($args{reqcallback} and !$args{nomsg}); foreach my $srvtype (@srvtypes) { send_service_request_single(%args, ifacemap => $interfaces, SrvType => $srvtype); } $rspcount1 = 0; } } } #end nowait } #end of if( unicast ) foreach my $entry (keys %rethash) { handle_new_slp_entity($rethash{$entry}); } if (xCAT::Utils->isAIX()) { foreach my $iface (keys %{$interfaces}) { foreach my $sip (@{ $interfaces->{$iface}->{ipv4addrs} }) { my $ip = $sip; $ip =~ s/\/(.*)//; my $maskbits = $1; my $runcmd = `route delete 239.255.255.253 $ip`; } } } return (\%searchmacs, $sendcount, $rspcount); } sub process_slp_packet { my %args = @_; my $sockaddy = $args{sockaddr}; my $socket = $args{'socket'}; my $packet = $args{packet}; my $parsedpacket = removeslpheader($packet); if ($parsedpacket->{FunctionId} == 2) { #Service Reply parse_service_reply($parsedpacket->{payload}, $parsedpacket); unless (ref $parsedpacket->{service_urls} and scalar @{ $parsedpacket->{service_urls} }) { return undef; } if ($parsedpacket->{attributes} && get_mac_for_addr($args{peername})) { #service reply had ext. Stop here if has gotten attributes and got mac. #continue the unicast request for service attributes if cannot find mac for peernode return $parsedpacket; #don't bother sending attrrequest, already got it in first packet } my $srvtype = $xid_to_srvtype_map{ $parsedpacket->{Xid} }; my $packet = generate_attribute_request(%args, SrvType => $srvtype); $sendhash{ $args{peername} }->{package} = $packet; $sendhash{ $args{peername} }->{sockaddy} = $sockaddy; $serrpy++; $socket->send($packet, 0, $sockaddy); return undef; } elsif ($parsedpacket->{FunctionId} == 7) { #attribute reply $attrpy++; $parsedpacket->{SrvType} = $xid_to_srvtype_map{ $parsedpacket->{Xid} }; $parsedpacket->{attributes} = parse_attribute_reply($parsedpacket->{payload}); my $attributes = $parsedpacket->{attributes}; my $type = ${ $attributes->{'type'} }[0]; return undef unless ($type); #delete $parsedpacket->{payload}; return $parsedpacket; } else { return undef; } } sub parse_attribute_reply { my $contents = shift; my @payload = unpack("C*", $contents); if ($payload[0] != 0 or $payload[1] != 0) { return {}; } splice(@payload, 0, 2); return parse_attribute_list(\@payload); } sub parse_attribute_list { my $payload = shift; my $attrlength = ($payload->[0] << 8) + $payload->[1]; splice(@$payload, 0, 2); my @attributes = splice(@$payload, 0, $attrlength); my $attrstring = pack("C*", @attributes); my %attribs; #now we have a string... my $lastattrstring; while ($attrstring) { if ($lastattrstring eq $attrstring) { #infinite loop $attribs{unparsed_attribdata} = $attrstring; last; } $lastattrstring = $attrstring; if ($attrstring =~ /^\(/) { $attrstring =~ s/([^)]*\)),?//; my $attrib = $1; $attrib =~ s/^\(//; $attrib =~ s/\),?$//; $attrib =~ s/=(.*)$//; $attribs{$attrib} = []; my $valstring = $1; if (defined $valstring) { foreach (split /,/, $valstring) { push @{ $attribs{$attrib} }, $_; } } } else { $attrstring =~ s/([^,]*),?//; $attribs{$1} = []; } } return \%attribs; } sub generate_attribute_request { my %args = @_; my $srvtype = $args{SrvType}; my $scope = "DEFAULT"; if ($args{Scopes}) { $scope = $args{Scopes}; } my $packet = pack("C*", 0, 0); #no prlist my $service = $srvtype; $service =~ s!://.*!!; my $length = length($service); $packet .= pack("C*", ($length >> 8), ($length & 0xff)); $length = length($scope); $packet .= $service . pack("C*", ($length >> 8), ($length & 0xff)) . $scope; $packet .= pack("C*", 0, 0, 0, 0); my $header = genslpheader($packet, FunctionId => 6); $xid_to_srvtype_map{ $xid++ } = $srvtype; return $header . $packet; #$args{'socket'}->send($header.$packet,0,$args{sockaddry}); } sub parse_service_reply { my $packet = shift; my $parsedpacket = shift; my @reply = unpack("C*", $packet); if ($reply[0] != 0 or $reply[1] != 0) { return (); } if ($parsedpacket->{extoffset}) { my @extdata = splice(@reply, $parsedpacket->{extoffset} - $parsedpacket->{currentoffset}); $parsedpacket->{currentoffset} = $parsedpacket->{extoffset}; parse_extension(\@extdata, $parsedpacket); } my $numurls = ($reply[2] << 8) + $reply[3]; splice(@reply, 0, 4); while ($numurls--) { push @{ $parsedpacket->{service_urls} }, extract_next_url(\@reply); } return; } sub parse_extension { my $extdata = shift; my $parsedpacket = shift; my $extid = ($extdata->[0] << 8) + $extdata->[1]; my $nextext = (($extdata->[2]) << 16) + (($extdata->[3]) << 8) + $extdata->[4]; if ($nextext) { my @nextext = splice(@$extdata, $nextext - $parsedpacket->{currentoffset}); $parsedpacket->{currentoffset} = $nextext; parse_extension(\@nextext, $parsedpacket); } splice(@$extdata, 0, 5); if ($extid == 2) { #this is defined in RFC 3059, attribute list extension #employed by AMM for one... my $urllen = ((shift @$extdata) << 8) + (shift @$extdata); splice @$extdata, 0, $urllen; #throw this out for now.. $parsedpacket->{attributes} = parse_attribute_list($extdata); } } sub extract_next_url { #section 4.3 url entries my $payload = shift; splice(@$payload, 0, 3); # discard reserved and lifetime which we will not bother using my $urllength = ((shift @$payload) << 8) + (shift @$payload); my @url = splice(@$payload, 0, $urllength); my $authblocks = shift @$payload; unless ($authblocks == 0) { $payload = []; #TODO: skip/use auth blocks if needed to get at more URLs } return pack("C*", @url); } sub send_service_request_single { my %args = @_; my $packet = generate_service_request(%args); my $interfaces = $args{ifacemap}; #get_interfaces(%args); my $socket = $args{'socket'}; my @v6addrs; my $v6addr; if ($ip6support) { my $hash = getmulticasthash($args{SrvType}); my $target = "ff02::1:$hash"; my ($fam, $type, $proto, $name); ($fam, $type, $proto, $v6addr, $name) = Socket6::getaddrinfo($target, "svrloc", Socket6::AF_INET6(), SOCK_DGRAM, 0); push @v6addrs, $v6addr; ($fam, $type, $proto, $v6addr, $name) = Socket6::getaddrinfo("ff01::1:$hash", "svrloc", Socket6::AF_INET6(), SOCK_DGRAM, 0); push @v6addrs, $v6addr; } my $ipv4mcastaddr = inet_aton("239.255.255.253"); #per rfc 2608 my $ipv4sockaddr = sockaddr_in(427, $ipv4mcastaddr); foreach my $iface (keys %{$interfaces}) { if ($ip6support) { setsockopt($socket, Socket6::IPPROTO_IPV6(), IPV6_MULTICAST_IF, pack("I", $interfaces->{$iface}->{scopeidx})); foreach $v6addr (@v6addrs) { $socket->send($packet, 0, $v6addr); } } foreach my $sip (@{ $interfaces->{$iface}->{ipv4addrs} }) { my $ip = $sip; $ip =~ s/\/(.*)//; my $maskbits = $1; if (xCAT::Utils->isAIX()) { my $runcmd = `route add 239.255.255.253 $ip`; } my $ipn = inet_aton($ip); #we are ipv4 only, this is ok my $ipnum = unpack("N", $ipn); $ipnum = $ipnum | (2**(32 - $maskbits)) - 1; my $bcastn = pack("N", $ipnum); my $bcastaddr = sockaddr_in(427, $bcastn); setsockopt($socket, 0, IP_MULTICAST_IF, $ipn); $socket->send($packet, 0, $ipv4sockaddr); $socket->send($packet, 0, $bcastaddr); } } } sub get_interfaces { #TODO: AIX tolerance, no subprocess, include/exclude interface(s) my %ifacemap; my $payingattention = 0; my $interface; my $keepcurrentiface; # AIX part if (xCAT::Utils->isAIX()) { $ip6support = 0; my $result = `ifconfig -a`; my @nics = $result =~ /(\w+\d+)\: flags=/g; my @adapter = split /\w+\d+:\s+flags=/, $result; for (my $i = 0 ; $i < scalar(@adapter) ; $i++) { $_ = $adapter[ $i + 1 ]; if (!($_ =~ /LOOPBACK/) and $_ =~ /UP(,|>)/ and $_ =~ /BROADCAST/) { my @ip = split /\n/; for my $entry (@ip) { if ($entry =~ /broadcast\s+/ and $entry =~ /^\s*inet\s+(\d+\.\d+\.\d+\.\d+)/) { my $tmpip = $1; if ($entry =~ /netmask\s+(0x\w+)/) { my $mask = hex($1); my $co = 31; my $count = 0; while ($co + 1) { if ((($mask & (2**$co)) >> $co) == 1) { $count++; } $co--; } $tmpip = $tmpip . '/' . $count; } push @{ $ifacemap{ $nics[$i] }->{ipv4addrs} }, $tmpip; if ($nics[$i] =~ /\w+(\d+)/) { $ifacemap{ $nics[$i] }->{scopeidx} = $1 + 2; } } } } } } else { my @ipoutput = `ip addr`; foreach my $line (@ipoutput) { if ($line =~ /^\d/) { # new interface, new context.. if ($interface and not $keepcurrentiface) { #don't bother reporting unusable nics delete $ifacemap{$interface}; } $keepcurrentiface = 0; unless ($line =~ /MULTICAST/) { #don't care if it isn't multicast capable $payingattention = 0; next; } $payingattention = 1; $line =~ /^([^:]*): ([^:]*):/; $interface = $2; $ifacemap{$interface}->{scopeidx} = $1; } unless ($payingattention) { next; } #don't think about lines unless in context of paying attention. if ($line =~ /inet/) { $keepcurrentiface = 1; } if ($line =~ /\s+inet\s+(\S+)\s/) { #got an ipv4 address, store it push @{ $ifacemap{$interface}->{ipv4addrs} }, $1; } } } return \%ifacemap; } # discovery is "service request", rfc 2608 # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Service Location header (function = SrvRqst = 1) | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | length of | String \ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | length of | String \ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | length of | String \ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | length of predicate string | Service Request \ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | length of string | String \ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ sub generate_service_request { my %args = @_; my $srvtype = $args{SrvType}; my $scope = "DEFAULT"; if ($args{Scopes}) { $scope = $args{Scopes}; } my $prlist = $gprlist; my $prlength = length($prlist); my $packet = pack("C*", ($prlength >> 8), ($prlength & 0xff)); $packet .= $prlist; my $length = length($srvtype); $packet .= pack("C*", ($length >> 8), ($length & 0xff)); $packet .= $srvtype; $length = length($scope); $packet .= pack("C*", ($length >> 8), ($length & 0xff)); $packet .= $scope; #no ldap predicates, and no auth, so zeroes.. $packet .= pack("C*", 0, 0, 0, 0); $packet .= pack("C*", 0, 2, 0, 0, 0, 0, 0, 0, 0, 0); my $extoffset = length($srvtype) + length($scope) + length($prlist) + 10; my $header = genslpheader($packet, Multicast => 1, FunctionId => 1, ExtOffset => $extoffset); $xid_to_srvtype_map{ $xid++ } = $srvtype; return $packet = $header . $packet; } # SLP header from RFC 2608 # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Version | Function-ID | Length | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Length, contd.|O|F|R| reserved |Next Ext Offset| # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Next Extension Offset, contd.| XID | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Language Tag Length | Language Tag \ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ sub removeslpheader { my $packet = shift; my %parsedheader; my @payload = unpack("C*", $packet); $parsedheader{Version} = shift @payload; $parsedheader{FunctionId} = shift @payload; splice(@payload, 0, 3); #remove length splice(@payload, 0, 2); #TODO: parse flags my $nextoffset = ((shift @payload) << 16) + ((shift @payload) << 8) + (shift @payload); $parsedheader{Xid} = ((shift @payload) << 8) + (shift @payload); my $langlen = ((shift @payload) << 8) + (shift @payload); $parsedheader{lang} = pack("C*", splice(@payload, 0, $langlen)); $parsedheader{payload} = pack("C*", @payload); if ($nextoffset != 0) { #correct offset since header will be removed $parsedheader{currentoffset} = 14 + $langlen; $parsedheader{extoffset} = $nextoffset; } return \%parsedheader; } sub genslpheader { my $packet = shift; my %args = @_; my $flaghigh = 0; my $flaglow = 0; #this will probably never ever ever change if ($args{Multicast}) { $flaghigh |= 0x20; } my $extoffset = 0; if ($args{ExtOffset}) { $extoffset = $args{ExtOffset} + 16; } my @extoffset = (($extoffset >> 16), (($extoffset >> 8) & 0xff), ($extoffset & 0xff)); my $length = length($packet) + 16; #our header is 16 bytes due to lang tag invariance if ($length > 1400) { die "Overflow not supported in xCAT SLP"; } return pack("C*", 2, $args{FunctionId}, ($length >> 16), ($length >> 8) & 0xff, $length & 0xff, $flaghigh, $flaglow, @extoffset, $xid >> 8, $xid & 0xff, 0, 2) . "en"; } unless (caller) { #time to provide unit testing/example usage #somewhat fancy invocation with multiple services and callback for #results on-the-fly require Data::Dumper; Data::Dumper->import(); my $srvtypes = [ "service:management-hardware.IBM:chassis-management-module", "service:management-hardware.IBM:integrated-management-module2", "service:management-hardware.IBM:management-module", "service:management-hardware.IBM:cec-service-processor" ]; xCAT::SLP::dodiscover(SrvTypes => $srvtypes, Callback => sub { print Dumper(@_) }); #example 2: simple invocation of a single service type $srvtypes = "service:management-hardware.IBM:chassis-management-module"; print Dumper(xCAT::SLP::dodiscover(SrvTypes => $srvtypes)); #TODO: pass-in socket and not wait inside SLP.pm example } ########################################### # Parse the slp resulte data ########################################### sub handle_new_slp_entity { my $data = shift; delete $data->{sockaddr}; #won't need it my $mac = get_mac_for_addr($data->{peername}); unless ($mac) { return; } $searchmacs{$mac} = $data; } ########################################### # Get mac addresses ########################################### sub get_mac_for_addr { my $neigh; my $addr = shift; if ($addr =~ /:/) { get_ipv6_neighbors(); return $ip6neigh{$addr}; } else { get_ipv4_neighbors(); return $ip4neigh{$addr}; } } ########################################### # Get ipv4 mac addresses ########################################### sub get_ipv4_neighbors { if (xCAT::Utils->isAIX()) { my @ipdata = `arp -a`; %ip6neigh = (); for my $entry (@ipdata) { if ($entry =~ /(\d+\.\d+\.\d+\.\d+)/) { my $ip = $1; #if ($entry =~ /at (\w+\:\w+\:\w+\:\w+\:\w+\:\w+)/) { # $ip4neigh{$ip}=$1; if ($entry =~ /at (\w+)\:(\w+)\:(\w+)\:(\w+)\:(\w+)\:(\w+)/) { #$ip4neigh{$ip}=$1.$2.$3.$4.$5.$6; $ip4neigh{$ip} = sprintf("%02s%02s%02s%02s%02s%02s", $1, $2, $3, $4, $5, $6); } } } } else { #TODO: something less 'hacky' my @ipdata = `ip -4 neigh`; %ip6neigh = (); foreach (@ipdata) { if (/^(\S*)\s.*lladdr\s*(\S*)\s/) { $ip4neigh{$1} = $2; } } } } ########################################### # Get ipv6 mac addresses ########################################### sub get_ipv6_neighbors { #TODO: something less 'hacky' my @ipdata = `ip -6 neigh`; %ip6neigh = (); foreach (@ipdata) { if (/^(\S*)\s.*lladdr\s*(\S*)\s/) { $ip6neigh{$1} = $2; } } } sub send_message { my $callback = shift; my $ecode = shift; my $msg = shift; my %output; $output{errorcode} = $ecode; $output{data} = $msg; $callback->(\%output); } 1;