2010-01-27 14:58:18 +00:00
#!/usr/bin/perl
# IBM(c) 2107 EPL license http://www.eclipse.org/legal/epl-v10.html
#(C)IBM Corp
#modified by jbjohnso@us.ibm.com
#This module abstracts the session management aspects of IPMI
package xCAT::IPMI ;
2010-02-12 19:24:25 +00:00
use Carp qw/confess cluck/ ;
2010-01-27 14:58:18 +00:00
BEGIN
{
$ ::XCATROOT = $ ENV { 'XCATROOT' } ? $ ENV { 'XCATROOT' } : '/opt/xcat' ;
}
use lib "$::XCATROOT/lib/perl" ;
use strict ;
use warnings "all" ;
2012-07-30 19:22:46 +00:00
use Time::HiRes qw/time/ ;
2010-01-27 14:58:18 +00:00
2012-01-27 16:53:48 +00:00
use IO::Socket::INET qw/!AF_INET6 !PF_INET6/ ;
2012-11-05 14:36:04 +00:00
my $ initialtimeout = 0.5 ;
2012-09-14 15:01:43 +00:00
use constant STATE_OPENSESSION = > 1 ;
use constant STATE_EXPECTINGRAKP2 = > 2 ;
use constant STATE_EXPECTINGRAKP4 = > 3 ;
use constant STATE_ESTABLISHED = > 4 ;
2012-10-03 19:56:35 +00:00
use constant STATE_FAILED = > 5 ;
2012-09-16 03:53:43 +00:00
#my $ipmidbg;
#open($ipmidbg,">","/tmp/ipmidbg");
#sub dprint {
# return;
# my $self = shift;
# foreach (@_) {
# foreach (split /\n/,$_) {
# print $ipmidbg $self->{bmc}.": ".$_."\n";
# }
# }
#}
2012-01-27 16:53:48 +00:00
2011-12-13 19:57:29 +00:00
my $ doipv6 = eval {
2012-03-08 15:17:20 +00:00
require Socket6 ;
2011-12-13 19:57:29 +00:00
require IO::Socket::INET6 ;
IO::Socket::INET6 - > import ( ) ;
1 ;
} ;
2010-01-27 14:58:18 +00:00
use IO::Select ;
2011-04-11 13:51:09 +00:00
#use Data::Dumper;
2010-01-27 14:58:18 +00:00
use Digest::MD5 qw/md5/ ;
2011-01-10 20:25:35 +00:00
my $ pendingpackets = 0 ;
2012-11-04 17:25:38 +00:00
my % tabooseq ;
2011-01-10 20:25:35 +00:00
my $ maxpending ; #determined dynamically based on rcvbuf detection
2010-01-28 03:42:24 +00:00
my $ ipmi2support = eval {
require Digest::SHA1 ;
Digest::SHA1 - > import ( qw/sha1/ ) ;
2010-01-28 22:06:22 +00:00
require Digest::HMAC_SHA1 ;
Digest::HMAC_SHA1 - > import ( qw/hmac_sha1/ ) ;
2010-01-28 03:42:24 +00:00
1 ;
} ;
my $ aessupport ;
if ( $ ipmi2support ) {
$ aessupport = eval {
require Crypt::Rijndael ;
require Crypt::CBC ;
1 ;
} ;
}
2010-01-27 14:58:18 +00:00
sub hexdump {
foreach ( @ _ ) {
printf "%02X " , $ _ ;
}
2010-01-28 03:42:24 +00:00
print "\n" ;
2010-01-27 14:58:18 +00:00
}
my % payload_types = ( #help readability in certain areas of code by specifying payload by name rather than number
'ipmi' = > 0 ,
'sol' = > 1 ,
'rmcpplusopenreq' = > 0x10 ,
'rmcpplusopenresponse' = > 0x11 ,
'rakp1' = > 0x12 ,
'rakp2' = > 0x13 ,
'rakp3' = > 0x14 ,
'rakp4' = > 0x15 ,
2010-01-28 03:42:24 +00:00
) ;
2013-01-02 18:31:14 +00:00
my % rmcp_codes = ( #human friendly translations of rmcp+ code numbers
1 = > "Insufficient resources to create new session (wait for existing sessions to timeout)" ,
2 = > "Invalid Session ID" , #this shouldn't occur...
3 = > "Invalid payload type" , #shouldn't occur..
4 = > "Invalid authentication algorithm" , #if this happens, we need to enhance our mechanism for detecting supported auth algorithms
5 = > "Invalid integrity algorithm" , #same as above
6 = > "No matching authentication payload" ,
7 = > "No matching integrity payload" ,
8 = > "Inactive Session ID" , #this suggests the session was timed out while trying to negotiate, shouldn't happen
9 = > "Invalid role" ,
0xa = > "Unauthorised role or privilege level requested" ,
0xb = > "Insufficient resources to create a session at the requested role" ,
0xc = > "Invalid username length" ,
0xd = > "Unauthorized name" ,
0xe = > "Unauthorized GUID" ,
0xf = > "Invalid integrity check value" ,
0x10 = > "Invalid confidentiality algorithm" ,
0x11 = > "No cipher suite match with proposed security algorithms" ,
0x12 = > "Illegal or unrecognized parameter" , #have never observed this, would most likely mean a bug in xCAT or IPMI device
) ;
2010-01-27 14:58:18 +00:00
my $ socket ; #global socket for all sessions to share. Fun fun
my $ select = IO::Select - > new ( ) ;
my % bmc_handlers ; #hash from bmc address to a live session management object.
2010-01-28 03:42:24 +00:00
#only one allowed at a time per bmc
2010-01-27 14:58:18 +00:00
my % sessions_waiting ; #track session objects that may want to retry a packet, value is timestamp to 'wake' object for retransmit
sub new {
my $ proto = shift ;
my $ class = ref $ proto || $ proto ;
my $ self = { } ;
bless $ self , $ class ;
my % args = @ _ ;
2010-01-28 03:42:24 +00:00
unless ( $ ipmi2support ) {
$ self - > { ipmi15only } = 1 ;
}
2010-01-27 14:58:18 +00:00
unless ( $ args { 'bmc' } and defined $ args { 'userid' } and defined $ args { 'password' } ) {
2010-02-10 20:05:00 +00:00
$ self - > { error } = "bmc, userid, and password must be specified" ;
return $ self ;
2010-01-27 14:58:18 +00:00
}
foreach ( keys % args ) { #store all passed parameters
$ self - > { $ _ } = $ args { $ _ } ;
}
unless ( $ args { 'port' } ) { #default to port 623 unless specified
$ self - > { 'port' } = 623 ;
}
unless ( $ socket ) {
2011-12-13 19:57:29 +00:00
if ( $ doipv6 ) {
$ socket = IO::Socket::INET6 - > new ( Proto = > 'udp' ) ;
} else {
$ socket = IO::Socket::INET - > new ( Proto = > 'udp' ) ;
}
2011-01-10 20:25:35 +00:00
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 = $ socket - > sockopt ( SO_RCVBUF ) ;
if ( $ maxrcvbuf > $ rcvbuf ) {
$ socket - > sockopt ( SO_RCVBUF , $ maxrcvbuf / 2 ) ;
}
$ maxpending = $ maxrcvbuf / 1500 ; #probably could have maxpending go higher, but just go with typical MTU as a guess
} else { #We do not have a way to determine how high we could set RCVBUF, so read the current value and run with it
my $ rcvbuf = $ socket - > sockopt ( SO_RCVBUF ) ;
$ maxpending = $ rcvbuf / 1500 ; #probably could have maxpending go higher, but just go with typical MTU as a guess
}
2010-01-27 14:58:18 +00:00
$ select - > add ( $ socket ) ;
}
2010-02-10 20:05:00 +00:00
my $ bmc_n ;
2011-12-13 19:57:29 +00:00
my ( $ family , $ socktype , $ protocol , $ saddr , $ name , $ ip , $ service ) ;
if ( $ doipv6 ) {
( $ family , $ socktype , $ protocol , $ saddr , $ name ) = Socket6:: getaddrinfo ( $ self - > { bmc } , 623 , AF_UNSPEC , SOCK_DGRAM , 0 ) ;
2012-03-08 15:17:20 +00:00
( $ ip , $ service ) = Socket6:: getnameinfo ( $ saddr , Socket6:: NI_NUMERICHOST ( ) ) ;
2011-12-13 19:57:29 +00:00
}
unless ( $ saddr or $ bmc_n = inet_aton ( $ self - > { bmc } ) ) {
2010-02-10 20:05:00 +00:00
$ self - > { error } = "Could not resolve " . $ self - > { bmc } . " to an address" ;
return $ self ;
}
2011-12-13 19:59:42 +00:00
if ( $ ip and $ ip =~ /::ffff:\d+\.\d+\.+\d+\.\d+/ ) {
2011-12-13 19:57:29 +00:00
$ ip =~ s/::ffff:// ;
2011-12-13 19:59:42 +00:00
} elsif ( not $ ip and $ bmc_n ) {
2011-12-13 19:57:29 +00:00
$ ip = inet_ntoa ( $ bmc_n ) ;
}
$ bmc_handlers { $ ip } = $ self ;
if ( $ saddr ) {
$ self - > { peeraddr } = $ saddr ;
} else {
$ self - > { peeraddr } = sockaddr_in ( $ self - > { port } , $ bmc_n ) ;
}
2012-08-23 17:08:52 +00:00
$ self - > init ( ) ;
2010-01-27 14:58:18 +00:00
return $ self ;
}
sub login {
my $ self = shift ;
my % args = @ _ ;
if ( $ self - > { logged } ) {
$ args { callback } - > ( "SUCCESS" , $ args { callback_args } ) ;
return ;
}
$ self - > { onlogon } = $ args { callback } ;
$ self - > { onlogon_args } = $ args { callback_args } ;
2012-08-23 17:08:52 +00:00
$ self - > { logontries } = 5 ;
2010-01-27 14:58:18 +00:00
$ self - > get_channel_auth_cap ( ) ;
}
sub logout {
my $ self = shift ;
my % args = @ _ ;
$ self - > { onlogout } = $ args { callback } ;
$ self - > { onlogout_args } = $ args { callback_args } ;
2010-02-05 18:11:12 +00:00
unless ( $ self - > { logged } ) {
if ( $ self - > { onlogout } ) { $ self - > { onlogout } - > ( "SUCCESS" , $ self - > { onlogout_args } ) ; }
return ;
}
2012-09-15 13:23:22 +00:00
$ self - > { noretry } = 1 ;
2010-01-27 14:58:18 +00:00
$ self - > subcmd ( netfn = > 0x6 , command = > 0x3c , data = > $ self - > { sessionid } , callback = > \ & logged_out , callback_args = > $ self ) ;
2012-09-15 13:23:30 +00:00
$ self - > { logged } = 0 ; #seeing as how we are going to take it on faith the logout happened, do the callback now
if ( $ self - > { onlogout } ) {
$ self - > { onlogout } - > ( "SUCCESS" , $ self - > { onlogout_args } ) ;
}
2010-01-27 14:58:18 +00:00
}
sub logged_out {
2012-09-15 13:23:30 +00:00
return ;
2010-01-27 14:58:18 +00:00
my $ rsp = shift ;
my $ self = shift ;
2011-01-10 20:25:35 +00:00
if ( defined $ rsp - > { code } and $ rsp - > { code } == 0 ) {
2010-01-27 14:58:18 +00:00
$ self - > { logged } = 0 ;
if ( $ self - > { onlogout } ) {
2010-01-28 03:42:24 +00:00
$ self - > { onlogout } - > ( "SUCCESS" , $ self - > { onlogout_args } ) ;
2010-01-27 14:58:18 +00:00
}
} else {
2010-01-28 03:42:24 +00:00
if ( $ self - > { onlogout } ) {
$ self - > { onlogout } - > ( "ERROR:" , $ self - > { onlogout_args } ) ;
}
2010-01-27 14:58:18 +00:00
}
}
sub get_channel_auth_cap { #implement special case for session management command
my $ self = shift ;
if ( defined $ self - > { ipmi15only } ) {
$ self - > subcmd ( netfn = > 0x6 , command = > 0x38 , data = > [ 0x0e , 0x04 ] , callback = > \ & got_channel_auth_cap , callback_args = > $ self ) ;
} else {
$ self - > subcmd ( netfn = > 0x6 , command = > 0x38 , data = > [ 0x8e , 0x04 ] , callback = > \ & got_channel_auth_cap , callback_args = > $ self ) ;
}
2010-01-28 03:42:24 +00:00
#0x8e, set bit to signify recognition of IPMI 2.0 and request channel 'e', current.
#0x04, request administrator privilege
2010-01-27 14:58:18 +00:00
}
sub get_session_challenge {
my $ self = shift ;
my @ user ;
if ( $ self - > { userbytes } ) {
@ user = @ { $ self - > { userbytes } } ;
} else {
@ user = unpack ( "C*" , $ self - > { userid } ) ;
for ( my $ i = scalar @ user ; $ i < 16 ; $ i + + ) {
$ user [ $ i ] = 0 ;
}
$ self - > { userbytes } = \ @ user ;
}
$ self - > subcmd ( netfn = > 0x6 , command = > 0x39 , data = > [ 2 , @ user ] , callback = > \ & got_session_challenge , callback_args = > $ self ) ; #we only support MD5, we would have errored out if not supported
}
sub got_session_challenge {
my $ rsp = shift ;
my $ self = shift ;
my @ data = @ { $ rsp - > { data } } ;
my % localcodes = ( 0x81 = > "Invalid user name" , 0x82 = > "null user disabled" ) ;
my $ code = $ rsp - > { code } ; #just to save me some typing
2010-01-28 03:42:24 +00:00
if ( $ code ) {
my $ errtxt = sprintf ( "ERROR: Get challenge failed with %02xh" , $ code ) ;
if ( $ localcodes { $ code } ) {
$ errtxt . = " ($localcodes{$code})" ;
} #TODO: generic codes
2010-01-27 14:58:18 +00:00
2010-01-28 03:42:24 +00:00
$ self - > { onlogon } - > ( $ errtxt , $ self - > { onlogon_args } ) ;
return ;
}
2010-01-27 14:58:18 +00:00
$ self - > { sessionid } = [ splice @ data , 0 , 4 ] ;
$ self - > { authtype } = 2 ; #switch to auth mode
2010-01-28 03:42:24 +00:00
$ self - > activate_session ( @ data ) ;
2010-01-27 14:58:18 +00:00
}
sub activate_session {
my $ self = shift ;
my @ challenge = @ _ ;
my @ data = ( 2 , 4 , @ challenge , 1 , 0 , 0 , 0 ) ;
$ self - > subcmd ( netfn = > 0x6 , command = > 0x3a , data = > \ @ data , callback = > \ & session_activated , callback_args = > $ self ) ;
}
sub session_activated {
my $ rsp = shift ;
my $ self = shift ;
my $ code = $ rsp - > { code } ; #just to save me some typing
2010-01-28 03:42:24 +00:00
my % localcodes = (
0x81 = > "No available login slots" ,
0x82 = > "No available login slots for " . $ self - > { userid } ,
0x83 = > "No slot available as administrator" ,
0x84 = > "Session sequence number out of range" ,
0x85 = > "Invalid session ID" ,
0x86 = > $ self - > { userid } . " is not allowed to be Administrator or Administrator not allowed over network" ,
) ;
2010-01-27 14:58:18 +00:00
if ( $ code ) {
my $ errtxt = sprintf ( "ERROR: Unable to log in to BMC due to code %02xh" , $ code ) ;
if ( $ localcodes { $ code } ) {
$ errtxt . = " ($localcodes{$code})" ;
}
$ self - > { onlogon } - > ( $ errtxt , $ self - > { onlogon_args } ) ;
2012-07-26 13:10:39 +00:00
return ;
}
if ( $ rsp - > { error } ) {
$ self - > { onlogon } - > ( $ rsp - > { error } , $ self - > { onlogon_args } ) ;
return ;
2010-01-27 14:58:18 +00:00
}
2012-07-26 13:10:39 +00:00
my @ data = @ { $ rsp - > { data } } ;
2010-01-27 14:58:18 +00:00
$ self - > { sessionid } = [ splice @ data , 1 , 4 ] ;
$ self - > { sequencenumber } = $ data [ 1 ] + ( $ data [ 2 ] << 8 ) + ( $ data [ 3 ] << 16 ) + ( $ data [ 4 ] << 24 ) ;
$ self - > { sequencenumberbytes } = [ splice @ data , 1 , 4 ] ;
$ self - > set_admin_level ( ) ;
}
sub set_admin_level {
my $ self = shift ;
$ self - > subcmd ( netfn = > 0x6 , command = > 0x3b , data = > [ 4 ] , callback = > \ & admin_level_set , callback_args = > $ self ) ;
}
sub admin_level_set {
my $ rsp = shift ;
my $ self = shift ;
my % localcodes = (
2010-01-28 03:42:24 +00:00
0x80 = > $ self - > { userid } . " is not allowed administrator access" ,
0x81 = > "This user or channel is not allowed administrator access" ,
0x82 = > "Cannot disable User Level authentication" ,
) ;
2010-01-27 14:58:18 +00:00
my $ code = $ rsp - > { code } ;
if ( $ code ) {
my $ errtxt = sprintf ( "ERROR: Failed requesting administrator privilege %02xh" , $ code ) ;
if ( $ localcodes { $ code } ) {
$ errtxt . = " (" . $ localcodes { $ code } . ")" ;
}
$ self - > { onlogon } - > ( $ errtxt , $ self - > { onlogon_args } ) ;
} else {
$ self - > { logged } = 1 ;
$ self - > { onlogon } - > ( "SUCCESS" , $ self - > { onlogon_args } ) ;
}
}
sub got_channel_auth_cap {
my $ rsp = shift ;
my $ self = shift ;
2010-02-05 18:17:27 +00:00
if ( $ rsp - > { error } ) {
$ self - > { onlogon } - > ( "ERROR: " . $ rsp - > { error } , $ self - > { onlogon_args } ) ;
return ;
}
2010-01-27 14:58:18 +00:00
my $ code = $ rsp - > { code } ; #just to save me some typing
2010-01-28 03:42:24 +00:00
if ( $ code == 0xcc and not defined $ self - > { ipmi15only } ) { #ok, most likely a stupid ipmi 1.5 bmc
$ self - > { ipmi15only } = 1 ;
return $ self - > get_channel_auth_cap ( ) ;
}
2010-01-27 14:58:18 +00:00
if ( $ code != 0 ) {
$ self - > { onlogon } - > ( "ERROR: Get channel capabilities failed with $code" , $ self - > { onlogon_args } ) ;
return ;
}
my @ data = @ { $ rsp - > { data } } ;
$ self - > { currentchannel } = $ data [ 0 ] ;
2010-01-28 03:42:24 +00:00
if ( ( $ data [ 1 ] & 0b10000000 ) and ( $ data [ 3 ] & 0b10 ) ) {
$ self - > { ipmiversion } = '2.0' ;
}
2010-01-27 14:58:18 +00:00
if ( $ self - > { ipmiversion } eq '1.5' ) {
2010-01-28 03:42:24 +00:00
unless ( $ data [ 1 ] & 0b100 ) {
$ self - > { onlogon } - > ( "ERROR: MD5 is required but not enabeld or available on target BMC" , $ self - > { onlogon_args } ) ;
}
2010-01-27 14:58:18 +00:00
$ self - > get_session_challenge ( ) ;
} elsif ( $ self - > { ipmiversion } eq '2.0' ) { #do rmcp+
2010-01-28 03:42:24 +00:00
$ self - > open_rmcpplus_request ( ) ;
2010-01-27 14:58:18 +00:00
}
}
2010-01-28 03:42:24 +00:00
sub open_rmcpplus_request {
my $ self = shift ;
$ self - > { 'authtype' } = 6 ;
2012-09-16 03:53:43 +00:00
unless ( $ self - > { localsid } ) { $ self - > { localsid } = 358098297 ; } #this is an arbitrary number of no significance
2012-09-16 12:55:27 +00:00
$ self - > { localsid } += 1 ; #new session ID if we are relogging
2012-09-16 03:53:43 +00:00
my @ sidbytes = unpack ( "C4" , pack ( "N" , $ self - > { localsid } ) ) ;
$ self - > { sidm } = \ @ sidbytes ;
2012-09-15 00:57:28 +00:00
unless ( $ self - > { rmcptag } ) { $ self - > { rmcptag } = 1 ; }
$ self - > { rmcptag } += 1 ;
my @ payload = ( $ self - > { rmcptag } , #message tag,
2010-01-28 03:42:24 +00:00
0 , #requested privilege role, 0 is highest allowed
0 , 0 , #reserved
2012-09-16 03:53:43 +00:00
@ sidbytes ,
2010-01-28 03:42:24 +00:00
0 , 0 , 0 , 8 , 1 , 0 , 0 , 0 , #table 13-17, request sha
1 , 0 , 0 , 8 , 1 , 0 , 0 , 0 ) ; #sha integrity
if ( $ aessupport ) {
push @ payload , ( 2 , 0 , 0 , 8 , 1 , 0 , 0 , 0 ) ;
} else {
push @ payload , ( 2 , 0 , 0 , 8 , 0 , 0 , 0 , 0 ) ;
}
2012-09-14 15:01:43 +00:00
$ self - > { sessionestablishmentcontext } = STATE_OPENSESSION ;
2010-01-28 03:42:24 +00:00
$ self - > sendpayload ( payload = > \ @ payload , type = > $ payload_types { 'rmcpplusopenreq' } ) ;
}
2010-01-27 14:58:18 +00:00
sub checksum {
my $ self = shift ;
my $ sum = 0 ;
foreach ( @ _ ) {
$ sum += $ _ ;
}
$ sum = ~ $ sum + 1 ;
return ( $ sum & 0xff ) ;
}
sub subcmd {
my $ self = shift ;
my % args = @ _ ;
2012-11-04 17:25:38 +00:00
$ self - > { expectedcmd } = $ args { command } ;
$ self - > { expectednetfn } = $ args { netfn } + 1 ;
my $ seqincrement = 7 ;
while ( $ tabooseq { $ self - > { expectednetfn } } - > { $ self - > { expectedcmd } } - > { $ self - > { seqlun } } and $ seqincrement ) { #avoid using a seqlun formerly marked 'taboo', but don't advance by more than 7, just in case
$ tabooseq { $ self - > { expectednetfn } } - > { $ self - > { expectedcmd } } - > { $ self - > { seqlun } } - - ; #forgive a taboo lun over time...
$ self - > { seqlun } += 4 ; #increment by 1<<2
$ self - > { seqlun } & = 0xff ; #make sure we don't get too large a seqlun
$ seqincrement - - ; #assure seq number doesn't go beyond 7 even if it means going taboo, one enhancement would be to pick the *least* taboo instead of just giving up
}
2010-01-27 14:58:18 +00:00
my $ rsaddr = 0x20 ; #figrue 13-4, rssa by old code
my @ rnl = ( $ rsaddr , $ args { netfn } << 2 ) ;
2012-10-11 21:02:53 +00:00
my @ rest = ( $ self - > { rqaddr } , $ self - > { seqlun } , $ args { command } , @ { $ args { data } } ) ;
2010-01-27 14:58:18 +00:00
my @ payload = ( @ rnl , $ self - > checksum ( @ rnl ) , @ rest , $ self - > checksum ( @ rest ) ) ;
$ self - > { ipmicallback } = $ args { callback } ;
$ self - > { ipmicallback_args } = $ args { callback_args } ;
2010-01-28 22:06:22 +00:00
my $ type = $ payload_types { 'ipmi' } ;
if ( $ self - > { integrityalgo } ) {
$ type = $ type | 0b01000000 ; #add integrity
}
2010-01-29 04:15:52 +00:00
if ( $ self - > { confalgo } ) {
$ type = $ type | 0b10000000 ; #add secrecy
}
2012-02-27 17:49:29 +00:00
$ self - > sendpayload ( payload = > \ @ payload , type = > $ type , delayxmit = > $ args { delayxmit } ) ;
2010-01-27 14:58:18 +00:00
}
sub waitforrsp {
my $ self = shift ;
2012-08-08 19:52:02 +00:00
my % args = @ _ ;
2010-01-27 14:58:18 +00:00
my $ data ;
my $ peerport ;
my $ peerhost ;
my $ timeout ; #TODO: code to scan pending objects to find soonest retry deadline
my $ curtime = time ( ) ;
2012-08-08 19:52:02 +00:00
if ( defined $ args { timeout } ) { $ timeout = $ args { timeout } ; }
2010-02-05 18:11:12 +00:00
foreach ( keys % sessions_waiting ) {
2012-08-08 19:52:02 +00:00
if ( defined $ timeout and $ timeout == 0 ) { last ; } #once we get to zero, then there is no lower and anything else is a waste
if ( $ sessions_waiting { $ _ } - > { timeout } <= $ curtime ) {
$ timeout = 0 ; #this waitforrsp must go as quickly to retry as possible, but give it a chance this iteration to clear without timedout being called
#if something defferred entry into waitforrsp so long that there was no chance to check for response, this grants at least one shot at getting data
2010-02-05 18:11:12 +00:00
}
2010-01-27 14:58:18 +00:00
if ( defined $ timeout ) {
2010-02-05 18:11:12 +00:00
if ( $ timeout < $ sessions_waiting { $ _ } - > { timeout } - $ curtime ) {
2010-01-27 14:58:18 +00:00
next ;
}
}
2010-02-05 18:11:12 +00:00
$ timeout = $ sessions_waiting { $ _ } - > { timeout } - $ curtime ;
}
unless ( defined $ timeout ) {
return scalar ( keys % sessions_waiting ) ;
2010-01-27 14:58:18 +00:00
}
if ( $ select - > can_read ( $ timeout ) ) {
while ( $ select - > can_read ( 0 ) ) {
2012-07-25 20:19:20 +00:00
my @ ipmiq = ( ) ;
while ( $ select - > can_read ( 0 ) ) {
$ peerport = $ socket - > recv ( $ data , 1500 , 0 ) ;
push @ ipmiq , [ $ peerport , $ data ] ;
}
while ( @ ipmiq ) {
my $ datagram = shift @ ipmiq ;
( $ peerport , $ data ) = @$ datagram ;
route_ipmiresponse ( $ peerport , unpack ( "C*" , $ data ) ) ;
while ( $ select - > can_read ( 0 ) ) {
$ peerport = $ socket - > recv ( $ data , 1500 , 0 ) ;
push @ ipmiq , [ $ peerport , $ data ] ;
}
}
2012-07-26 13:10:39 +00:00
}
2010-01-27 14:58:18 +00:00
}
2012-08-08 19:52:02 +00:00
foreach ( keys % sessions_waiting ) { #now that we have given all incoming packets a chance, if some sessions were past due when we entered
#take timeout response action now
if ( $ sessions_waiting { $ _ } - > { timeout } <= $ curtime ) {
my $ session = $ sessions_waiting { $ _ } - > { ipmisession } ;
delete $ sessions_waiting { $ _ } ;
$ pendingpackets -= 1 ;
$ session - > timedout ( ) ;
next ;
}
}
2010-01-27 14:58:18 +00:00
return scalar ( keys % sessions_waiting ) ;
}
2010-02-05 18:11:12 +00:00
sub timedout {
my $ self = shift ;
2012-09-16 03:53:43 +00:00
unless ( ref $ self - > { pendingargs } ) {
return ;
}
2012-09-15 00:57:44 +00:00
$ self - > { nowait } = 1 ;
2012-11-05 14:36:04 +00:00
$ self - > { timeout } += 1 ; #$self->{timeout}*2;
2012-09-15 13:23:22 +00:00
if ( $ self - > { noretry } ) { return ; }
2012-11-04 17:25:38 +00:00
if ( $ self - > { timeout } > 5 ) { #giveup, really
2012-11-05 14:36:04 +00:00
$ self - > { timeout } = $ initialtimeout + ( 0.5 * rand ( ) ) ;
2010-02-05 18:17:27 +00:00
my $ rsp = { } ;
$ rsp - > { error } = "timeout" ;
$ self - > { ipmicallback } - > ( $ rsp , $ self - > { ipmicallback_args } ) ;
2012-09-15 00:57:44 +00:00
$ self - > { nowait } = 0 ;
2010-02-05 18:11:12 +00:00
return ;
2012-10-03 19:56:29 +00:00
} elsif ( $ self - > { sessionestablishmentcontext } == STATE_FAILED ) {
return ;
2010-02-05 18:11:12 +00:00
}
2012-09-15 00:57:28 +00:00
if ( $ self - > { sessionestablishmentcontext } == STATE_OPENSESSION ) { #in this particular case, we want to craft a new rmcp session request with a new client side session id, to aid in distinguishing retry from new
$ self - > open_rmcpplus_request ( ) ;
2012-09-16 03:53:43 +00:00
#experimintation has showed rakp1 and 3 are best done with a straightforward retry, not something fancy...
#stale rakp3 in a ipmi2 implementation that can't handle it will be detected through rmcp status code rather than assuming we must start over.
2012-09-15 00:57:28 +00:00
} elsif ( $ self - > { sessionestablishmentcontext } == STATE_EXPECTINGRAKP2 ) { #in this particular case, we want to craft a new rmcp session request with a new client side session id, to aid in distinguishing retry from new
2012-09-16 03:53:43 +00:00
$ self - > relog ( ) ;
2012-09-15 00:57:28 +00:00
} elsif ( $ self - > { sessionestablishmentcontext } == STATE_EXPECTINGRAKP4 ) { #in this particular case, we want to craft a new rmcp session request with a new client side session id, to aid in distinguishing retry from new
2012-09-16 03:53:43 +00:00
$ self - > relog ( ) ;
2012-09-15 00:57:28 +00:00
} else {
2012-11-04 17:25:38 +00:00
$ self - > { hasretried } = 1 ; #remember that we have retried at the moment
2012-09-15 00:57:28 +00:00
$ self - > sendpayload ( % { $ self - > { pendingargs } } , nowait = > 1 ) ; #do not induce the xmit to wait for packets, just spit it out. timedout is in a wait-for-packets loop already, so it's fine
}
2012-09-15 00:57:44 +00:00
$ self - > { nowait } = 0 ;
2010-02-05 18:11:12 +00:00
}
2010-01-27 14:58:18 +00:00
sub route_ipmiresponse {
my $ sockaddr = shift ;
my @ rsp = @ _ ;
unless (
$ rsp [ 0 ] == 0x6 and
$ rsp [ 2 ] == 0xff and
$ rsp [ 3 ] == 0x07 ) {
return ; #ignore non-ipmi packets
}
my $ host ;
my $ port ;
2011-12-13 19:57:29 +00:00
#($port,$host) = sockaddr_in6($sockaddr);
#$host = inet_ntoa($host);
if ( $ doipv6 ) {
2012-03-08 15:17:20 +00:00
( $ host , $ port ) = Socket6:: getnameinfo ( $ sockaddr , Socket6:: NI_NUMERICHOST ( ) ) ;
2011-12-13 19:57:29 +00:00
} else {
( $ port , $ host ) = sockaddr_in ( $ sockaddr ) ;
$ host = inet_ntoa ( $ host ) ;
}
if ( $ host =~ /::ffff:\d+\.\d+\.+\d+\.\d+/ ) {
$ host =~ s/::ffff:// ;
}
2010-01-27 14:58:18 +00:00
if ( $ bmc_handlers { $ host } ) {
2011-01-10 20:25:35 +00:00
$ pendingpackets -= 1 ;
2010-01-27 14:58:18 +00:00
$ bmc_handlers { $ host } - > handle_ipmi_packet ( @ rsp ) ;
}
}
sub handle_ipmi_packet {
2010-02-12 19:24:25 +00:00
#return zero if we like the response
2010-01-27 14:58:18 +00:00
my $ self = shift ;
my @ rsp = @ _ ;
if ( $ rsp [ 4 ] == 0 or $ rsp [ 4 ] == 2 ) { #IPMI 1.5 (check 0 assumption...)
my $ remsequencenumber = $ rsp [ 5 ] + $ rsp [ 6 ] >> 8 + $ rsp [ 7 ] >> 16 + $ rsp [ 8 ] >> 24 ;
if ( $ self - > { remotesequencenumber } and $ remsequencenumber < $ self - > { remotesequencenumber } ) {
2010-02-12 19:24:25 +00:00
return 5 ; #ignore malformed sequence number
2010-01-27 14:58:18 +00:00
}
$ self - > { remotesequencenumber } = $ remsequencenumber ;
$ self - > { remotesequencebytes } = [ @ rsp [ 5 .. 8 ] ] ;
if ( $ rsp [ 4 ] != $ self - > { authtype } ) {
return 2 ; # not thinking about packets that do not match our preferred auth type
}
unless ( $ rsp [ 9 ] == $ self - > { sessionid } - > [ 0 ] and
$ rsp [ 10 ] == $ self - > { sessionid } - > [ 1 ] and
$ rsp [ 11 ] == $ self - > { sessionid } - > [ 2 ] and
$ rsp [ 12 ] == $ self - > { sessionid } - > [ 3 ] ) {
return 1 ; #this response does not match our current session id, ignore it
}
my @ authcode = ( ) ;
if ( $ rsp [ 4 ] == 2 ) {
@ authcode = splice @ rsp , 13 , 16 ;
}
my @ payload = splice ( @ rsp , 14 , $ rsp [ 13 ] ) ;
if ( @ authcode ) { #authcode is longer than 0, check it
$ self - > { checkremotecode } = 1 ;
my @ expectedauthcode = $ self - > ipmi15authcode ( @ payload ) ;
$ self - > { checkremotecode } = 0 ;
foreach ( 0 .. 15 ) {
if ( $ expectedauthcode [ $ _ ] != $ authcode [ $ _ ] ) {
return 3 ; #invalid authcode
}
}
}
2010-02-12 19:24:25 +00:00
return $ self - > parse_ipmi_payload ( @ payload ) ;
2010-01-27 14:58:18 +00:00
} elsif ( $ rsp [ 4 ] == 6 ) { #IPMI 2.0
2010-01-28 03:42:24 +00:00
if ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x11 ) {
2010-02-12 19:24:25 +00:00
return $ self - > got_rmcp_response ( splice @ rsp , 16 ) ; #the function always leaves ourselves waiting, no need to deregister
2010-01-28 22:06:22 +00:00
} elsif ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x13 ) {
2010-02-12 19:24:25 +00:00
return $ self - > got_rakp2 ( splice @ rsp , 16 ) ; #same as above
2010-01-28 22:06:22 +00:00
} elsif ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x15 ) {
2010-02-12 19:24:25 +00:00
return $ self - > got_rakp4 ( splice @ rsp , 16 ) ; #same as above
} elsif ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x0 ) { #ipmi payload, sophisticated logic to follow
2010-01-29 03:17:19 +00:00
my $ encrypted ;
if ( $ rsp [ 5 ] & 0b10000000 ) {
$ encrypted = 1 ;
}
unless ( $ rsp [ 5 ] & 0b01000000 ) {
2010-02-12 19:24:25 +00:00
return 3 ; #we refuse to examine unauthenticated packets in this context
2010-01-29 03:17:19 +00:00
}
2010-02-12 19:24:25 +00:00
splice ( @ rsp , 0 , 4 ) ; #ditch the rmcp header
my @ authcode = splice ( @ rsp , - 12 ) ; #strip away authcode and remember it
2010-01-29 03:17:19 +00:00
my @ expectedcode = unpack ( "C*" , hmac_sha1 ( pack ( "C*" , @ rsp ) , $ self - > { k1 } ) ) ;
splice ( @ expectedcode , 12 ) ;
foreach ( @ expectedcode ) {
unless ( $ _ == shift @ authcode ) {
2010-02-12 19:24:25 +00:00
return 3 ; #authcode bad, pretend it never existed
2010-01-29 03:17:19 +00:00
}
}
2012-09-16 03:53:43 +00:00
my $ thissid = unpack ( "N" , pack ( "C*" , $ rsp [ 2 ] , $ rsp [ 3 ] , $ rsp [ 4 ] , $ rsp [ 5 ] ) ) ;
unless ( $ thissid == $ self - > { localsid } ) {
2010-02-12 19:24:25 +00:00
return 1 ; #this response does not match our current session id, ignore it
}
my $ remsequencenumber = $ rsp [ 6 ] + $ rsp [ 7 ] >> 8 + $ rsp [ 8 ] >> 16 + $ rsp [ 9 ] >> 24 ;
if ( $ self - > { remotesequencenumber } and $ remsequencenumber < $ self - > { remotesequencenumber } ) {
return 5 ; #ignore malformed sequence number
}
$ self - > { remotesequencenumber } = $ remsequencenumber ;
2010-01-29 04:40:10 +00:00
my $ psize = $ rsp [ 10 ] + ( $ rsp [ 11 ] << 8 ) ;
my @ payload = splice ( @ rsp , 12 , $ psize ) ;
2010-01-29 03:17:19 +00:00
if ( $ encrypted ) {
2010-01-29 04:40:10 +00:00
my $ iv = pack ( "C*" , splice @ payload , 0 , 16 ) ;
2010-01-30 01:06:16 +00:00
my $ cipher = Crypt::CBC - > new ( - literal_key = > 1 , - key = > $ self - > { aeskey } , - cipher = > "Crypt::Rijndael" , - header = > "none" , - iv = > $ iv , - keysize = > 16 , - blocksize = > 16 , - padding = > \ & cbc_pad ) ;
my $ crypted = pack ( "C*" , @ payload ) ;
@ payload = unpack ( "C*" , $ cipher - > decrypt ( $ crypted ) ) ;
2010-01-29 03:17:19 +00:00
}
2010-02-12 19:24:25 +00:00
return $ self - > parse_ipmi_payload ( @ payload ) ;
} else {
return 6 ; #unsupported payload
2010-01-28 03:42:24 +00:00
}
2010-02-12 19:24:25 +00:00
} else {
return 7 ; #unsupported ASF traffic
2010-01-27 14:58:18 +00:00
}
}
2010-01-30 01:06:16 +00:00
sub cbc_pad {
my $ block = shift ;
my $ size = shift ;
my $ mode = shift ;
if ( $ mode eq 'e' ) {
my $ neededpad = $ size - length ( $ block ) %$ size ;
$ neededpad -= 1 ;
my @ pad = unpack ( "C*" , $ block ) ;
foreach ( 1 .. $ neededpad ) {
push @ pad , $ _ ;
}
push @ pad , $ neededpad ;
return pack ( "C*" , @ pad ) ;
} elsif ( $ mode eq 'd' ) {
my @ block = unpack ( "C*" , $ block ) ;
my $ count = pop @ block ;
2010-01-31 18:39:53 +00:00
unless ( $ count ) {
return pack ( "C*" , @ block ) ;
}
2010-01-30 01:06:16 +00:00
splice @ block , 0 - $ count ;
return pack ( "C*" , @ block ) ;
}
}
2010-01-28 22:06:22 +00:00
2010-01-28 03:42:24 +00:00
sub got_rmcp_response {
my $ self = shift ;
2010-01-28 22:06:22 +00:00
my @ data = @ _ ;
my $ byte = shift @ data ;
2012-09-14 15:01:43 +00:00
unless ( $ self - > { sessionestablishmentcontext } and $ self - > { sessionestablishmentcontext } != STATE_ESTABLISHED ) {
#we would ignore an RMCP+ open session response if we are not in an IPMI2 negotiation, so we have to have *some* state that isn't established for this to be kosher
2010-02-12 19:24:25 +00:00
return 9 ; #now's not the time for this response, ignore it
}
2012-09-15 00:57:28 +00:00
unless ( $ byte == $ self - > { rmcptag } ) { #make sure this rmcp response is specifically the last one we sent.... we don't want to happily proceed with the risk a retry request blew up our temp session id without letting us know
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte == 0x00 ) {
2013-01-02 18:31:14 +00:00
if ( $ rmcp_codes { $ byte } ) {
$ self - > { onlogon } - > ( "ERROR: " . $ rmcp_codes { $ byte } , $ self - > { onlogon_args } ) ; #TODO: errors
} else {
$ self - > { onlogon } - > ( "ERROR: $byte code on opening RMCP+ session" , $ self - > { onlogon_args } ) ; #TODO: errors
}
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte >= 4 ) {
$ self - > { onlogon } - > ( "ERROR: Cannot acquire sufficient privilege" , $ self - > { onlogon_args } ) ;
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
splice @ data , 0 , 5 ;
$ self - > { pendingsessionid } = [ splice @ data , 0 , 4 ] ;
2012-09-14 15:01:43 +00:00
#TODO: if we retried, and the first answer comes back but the second answer is dropped, log in will fail as we do not know our correct session id
#basically, we would have to retry open session requested until RAKP2 *confirmed* good
2010-01-28 22:06:22 +00:00
$ self - > send_rakp1 ( ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-28 22:06:22 +00:00
}
sub send_rakp3 {
2012-09-14 15:01:43 +00:00
#TODO: this is the point where OPEN RMCP SESSION REQUEST should have retry stopped, not send_rakp1
2010-01-28 22:06:22 +00:00
my $ self = shift ;
2012-09-15 00:57:28 +00:00
$ self - > { rmcptag } += 1 ;
my @ payload = ( $ self - > { rmcptag } , 0 , 0 , 0 , @ { $ self - > { pendingsessionid } } ) ;
2010-01-28 22:06:22 +00:00
my @ user = unpack ( "C*" , $ self - > { userid } ) ;
push @ payload , unpack ( "C*" , hmac_sha1 ( pack ( "C*" , @ { $ self - > { remoterandomnumber } } , @ { $ self - > { sidm } } , 4 , scalar @ user , @ user ) , $ self - > { password } ) ) ;
$ self - > sendpayload ( payload = > \ @ payload , type = > $ payload_types { 'rakp3' } ) ;
}
sub send_rakp1 {
my $ self = shift ;
2012-09-15 00:57:28 +00:00
$ self - > { rmcptag } += 1 ;
my @ payload = ( $ self - > { rmcptag } , 0 , 0 , 0 , @ { $ self - > { pendingsessionid } } ) ;
2010-01-28 22:06:22 +00:00
$ self - > { randomnumber } = [] ;
foreach ( 1 .. 16 ) {
my $ randomnumber = int ( rand ( 255 ) ) ;
push @ { $ self - > { randomnumber } } , $ randomnumber ;
}
push @ payload , @ { $ self - > { randomnumber } } ;
push @ payload , ( 4 , 0 , 0 ) ; # request admin
my @ user = unpack ( "C*" , $ self - > { userid } ) ;
push @ payload , scalar @ user ;
push @ payload , @ user ;
2012-09-15 00:57:28 +00:00
$ self - > { sessionestablishmentcontext } = STATE_EXPECTINGRAKP2 ;
2010-01-28 22:06:22 +00:00
$ self - > sendpayload ( payload = > \ @ payload , type = > $ payload_types { 'rakp1' } ) ;
}
2012-08-23 17:08:52 +00:00
sub init {
my $ self = shift ;
2012-09-16 03:53:43 +00:00
$ self - > { sessionestablishmentcontext } = 0 ;
2012-08-23 17:08:52 +00:00
$ self - > { 'sequencenumber' } = 0 ; #init sequence number
$ self - > { 'sequencenumberbytes' } = [ 0 , 0 , 0 , 0 ] ; #init sequence number
$ self - > { 'sessionid' } = [ 0 , 0 , 0 , 0 ] ; # init session id
$ self - > { 'authtype' } = 0 ; # first messages will have auth type of 0
$ self - > { 'ipmiversion' } = '1.5' ; # send first packet as 1.5
2012-11-05 14:36:04 +00:00
$ self - > { 'timeout' } = $ initialtimeout + ( 0.5 * rand ( ) ) ; #start at a quick timeout, increase on retry
2012-08-23 17:08:52 +00:00
$ self - > { 'seqlun' } = 0 ; #the IPMB seqlun combo, increment by 4s
2012-10-11 21:02:53 +00:00
$ self - > { rqaddr } = 0x81 ; #Per table '5-4' system sofware ids in the ipmi spec, we are allowed 0x81-0x8d software ids
#A problem with ipmi is that chatty commands (rinv) can mistake stale data for new if sequence number overflows
#for example, if 'get firmware information' command is retried, and happens to have sequence number 4,
#64 transactions later a reply to the retry comes up, the data is passed into the callback function because of ambiguity introduced by the
#overflowed sequence number
#to mitigate this, we will iterate rqaddr every time the seqlun counter overflows
#of course, this still means that rqaddr will, itself, overflow, but it mitigates things because:
#448 instead of 64 transactions are now required before ambiguity is possible
#A stale reply has to come in after the conversation has advanced at least 448 transactions, meaning longer delay on extraneous reply before this is a problem
#even if a stale reply comes in at *about* the right time, it has to match an exact multiple of 448 instead of 64, which is significantly less likely.
2012-10-25 19:53:58 +00:00
#I abandoned this strategy, but leaving it documented for posterity. The issue is that some operations don't like the client changing software id
#midstream (notably SDR reservations).
#ipmi spec does contain some guidance in the BT section of the spec 'Using the Seq Field'. There is guidance elsewhere about sequence numbers higher than 8 beyond current
#but I'm not sure if thet means 'session sequence number' or 'seq', which are very very different.
#one tangible recommendation is for the combination of command+seq+netfn to be unique, which can certainly help, so we should track netfn and command to make sure response
#matches at least the expected command and netfn.
#additionally, it looks like we could implement a sort of 'taboo' sequence number scheme. When we ascertain that we are retrying, we flag the current sequence number to be
#'taboo' in the future. When the time comes to increment sequence number, we mark the current combination of command+netfn+seq as 'taboo'
#should circumstances suggest that we are about to transmit a packet with a taboo combination, we bump sequence number until no longer taboo, no more than 8 bumps.
#if we should incur 7 bumps, clear the taboo list and continue on, hoping for best (pessimistically assuming the spec means seq number or that someone could at least interpret it that way)
#I'll implement this later...
2012-08-23 17:08:52 +00:00
$ self - > { 'logged' } = 0 ;
}
sub relog {
my $ self = shift ;
$ self - > init ( ) ;
$ self - > { logontries } -= 1 ;
$ self - > get_channel_auth_cap ( ) ;
}
2010-01-28 22:06:22 +00:00
sub got_rakp4 {
my $ self = shift ;
my @ data = @ _ ;
my $ byte = shift @ data ;
2012-09-14 15:01:43 +00:00
unless ( $ self - > { sessionestablishmentcontext } == STATE_EXPECTINGRAKP4 ) { #ignore rakp4 unless we are explicitly expecting RAKP4
2010-02-12 19:24:25 +00:00
return 9 ; #now's not the time for this response, ignore it
}
2012-09-15 00:57:28 +00:00
unless ( $ byte == $ self - > { rmcptag } ) { #make sure this rmcp response is specifically the last one we sent.... we don't want to happily proceed with the risk a retry request blew up our temp session id without letting us know
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte == 0x00 ) {
2012-09-16 03:53:43 +00:00
if ( ( $ byte == 0x02 ) and $ self - > { logontries } ) {
#ok, turns out an IPMI2 device may optimistically assume that since it has transmitted RAKP4, it's done with this whole RAKP exchange, thus
#code 2 can happen.... To workaround this, code 2 is taken as a cue to start over if we haven't got an rakp2 yet
$ self - > relog ( ) ;
}
if ( ( $ byte == 0x02 or $ byte == 15 ) and $ self - > { logontries } ) { # most likely scenario is that a retry earlier in the process invalided the flow this packet came in on, ignore it and hope the retries all sort out
#UPDATE: turns out open rmcp session request shenanigans were to blame, rakp2 straight retransmits seems safe
2012-09-14 15:01:43 +00:00
#the biggest risk: that we did not receive the correct rakp2, so the prudent thing to be doing in this time interval would be retrying RAKP1...
#ipmi2 session negotiation is a bit weird in how retries can corrupt state and we effectively should be rewinding a bit...
#TODO: think about retry logic hard to decide how many packets we can retry
#thought: can we match a failed RAKP2 to the last RAKP1 we transmitted? If we can, and we see the last RAKP1 was in fact the one this response is for, that
#would definitely mean we should rewinnd to open session rquest..
#ditto for rakp4, if we can confirm rakp is for the last transmitted rakp3, then we need to rewind to send_rakp1...
#$self->relog();
2012-08-23 17:08:52 +00:00
return ;
}
2013-01-02 18:31:14 +00:00
if ( $ rmcp_codes { $ byte } ) {
$ self - > { onlogon } - > ( "ERROR: " . $ rmcp_codes { $ byte } , $ self - > { onlogon_args } ) ; #TODO: errors
} else {
$ self - > { onlogon } - > ( "ERROR: $byte code on opening RMCP+ session" , $ self - > { onlogon_args } ) ; #TODO: errors
}
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
splice @ data , 0 , 6 ; #discard reserved bytes and session id
my @ expectauthcode = unpack ( "C*" , hmac_sha1 ( pack ( "C*" , @ { $ self - > { randomnumber } } , @ { $ self - > { pendingsessionid } } , @ { $ self - > { remoteguid } } ) , $ self - > { sik } ) ) ;
foreach ( @ expectauthcode [ 0 .. 11 ] ) {
unless ( $ _ == ( shift @ data ) ) {
2012-09-15 00:57:28 +00:00
#we'll just ignore this transgression...... *this time*
#$self->{onlogon}->("ERROR: failure in final rakp exchange message",$self->{onlogon_args});
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
}
$ self - > { sessionid } = $ self - > { pendingsessionid } ;
$ self - > { integrityalgo } = 'sha1' ;
2010-01-29 04:15:52 +00:00
if ( $ aessupport ) {
$ self - > { confalgo } = 'aes' ;
}
2010-01-29 02:32:47 +00:00
$ self - > { sequencenumber } = 1 ;
$ self - > { sequencenumberbytes } = [ 1 , 0 , 0 , 0 ] ;
2012-09-14 15:01:43 +00:00
$ self - > { sessionestablishmentcontext } = STATE_ESTABLISHED ; #will move on to relying upon session sequence number
2010-01-28 22:06:22 +00:00
$ self - > set_admin_level ( ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-28 22:06:22 +00:00
}
sub got_rakp2 {
my $ self = shift ;
my @ data = @ _ ;
my $ byte = shift @ data ;
2012-09-14 15:01:43 +00:00
unless ( $ self - > { sessionestablishmentcontext } >= STATE_EXPECTINGRAKP2 and $ self - > { sessionestablishmentcontext } != STATE_ESTABLISHED ) {
#we will bail out unless the state is either EXPECTINGRAKP2 or EXPECTINGRAKP4.
#the reason being that if an old rakp1 retry actually made it and we were just too aggressive, then a previous rakp2 is invalidated and invalid session id or the integrity check value is bad
2010-02-12 19:24:25 +00:00
return 9 ; #now's not the time for this response, ignore it
}
2012-09-15 00:57:28 +00:00
unless ( $ byte == $ self - > { rmcptag } ) { #make sure this rmcp response is specifically the last one we sent.... we don't want to happily proceed with the risk a retry request blew up our temp session id without letting us know
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte == 0x00 ) {
2012-09-14 15:01:43 +00:00
if ( $ byte == 0x02 ) { #invalid session id is almost certainly because a retry on rmcp+ open session response rendered our session id invalid, ignore this in the hope that we'll get an answer for our retry that invalidated us..
#$self->relog();
#TODO: probably should disable RAKP1 retry here... high likelihood that we'll just spew a bad RAKP1 and Open Session Request retry would be more appropriate to try to discern a valid session id
2012-08-23 17:08:52 +00:00
return ;
}
2013-01-02 18:31:14 +00:00
if ( $ rmcp_codes { $ byte } ) {
$ self - > { onlogon } - > ( "ERROR: " . $ rmcp_codes { $ byte } , $ self - > { onlogon_args } ) ; #TODO: errors
} else {
$ self - > { onlogon } - > ( "ERROR: $byte code on opening RMCP+ session" , $ self - > { onlogon_args } ) ; #TODO: errors
}
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
splice @ data , 0 , 6 ; # throw away reserved bytes, and session id, might need to check
$ self - > { remoterandomnumber } = [] ;
foreach ( 1 .. 16 ) {
push @ { $ self - > { remoterandomnumber } } , ( shift @ data ) ;
}
$ self - > { remoteguid } = [] ;
foreach ( 1 .. 16 ) {
push @ { $ self - > { remoteguid } } , ( shift @ data ) ;
}
#Data now represents authcode.. sha1 only..
my @ user = unpack ( "C*" , $ self - > { userid } ) ;
my $ ulength = scalar @ user ;
2012-09-16 03:53:43 +00:00
my $ hmacdata = pack ( "C*" , ( @ { $ self - > { sidm } } , @ { $ self - > { pendingsessionid } } , @ { $ self - > { randomnumber } } , @ { $ self - > { remoterandomnumber } } , @ { $ self - > { remoteguid } } , 4 , $ ulength , @ user ) ) ;
2010-01-28 22:06:22 +00:00
my @ expectedhash = ( unpack ( "C*" , hmac_sha1 ( $ hmacdata , $ self - > { password } ) ) ) ;
foreach ( 0 .. ( scalar ( @ expectedhash ) - 1 ) ) {
if ( $ expectedhash [ $ _ ] != $ data [ $ _ ] ) {
2012-10-03 19:56:29 +00:00
$ self - > { sessionestablishmentcontext } = STATE_FAILED ;
2010-01-28 22:06:22 +00:00
$ self - > { onlogon } - > ( "ERROR: Incorrect password provided" , $ self - > { onlogon_args } ) ;
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
}
$ self - > { sik } = hmac_sha1 ( pack ( "C*" , @ { $ self - > { randomnumber } } , @ { $ self - > { remoterandomnumber } } , 4 , $ ulength , @ user ) , $ self - > { password } ) ;
2010-01-29 02:32:47 +00:00
$ self - > { k1 } = hmac_sha1 ( pack ( "C*" , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ) , $ self - > { sik } ) ;
2010-01-29 04:15:52 +00:00
if ( $ aessupport ) {
$ self - > { k2 } = hmac_sha1 ( pack ( "C*" , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ) , $ self - > { sik } ) ;
my @ aeskey = unpack ( "C*" , $ self - > { k2 } ) ;
$ self - > { aeskey } = pack ( "C*" , ( splice @ aeskey , 0 , 16 ) ) ;
}
2012-09-14 15:01:43 +00:00
$ self - > { sessionestablishmentcontext } = STATE_EXPECTINGRAKP4 ;
2010-01-28 22:06:22 +00:00
$ self - > send_rakp3 ( ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-28 03:42:24 +00:00
}
2010-01-27 14:58:18 +00:00
sub parse_ipmi_payload {
my $ self = shift ;
my @ payload = @ _ ;
#for now, just trash the headers, this has been validated to death anyway
2010-02-12 19:24:25 +00:00
#except seqlun, that one is important
2013-01-29 21:23:38 +00:00
unless ( $ payload [ 4 ] == $ self - > { seqlun } and $ payload [ 1 ] >> 2 == $ self - > { expectednetfn } and $ payload [ 5 ] == $ self - > { expectedcmd } ) {
2012-10-11 21:02:53 +00:00
#both sequence number and arqaddr must match, because we are using rqaddr to extend the sequence number
2012-09-13 21:00:07 +00:00
#print "Successfully didn't get confused by stale response ".$payload[4]." and ".($self->{seqlun}-4)."\n";
2012-10-11 21:02:53 +00:00
#hexdump(@payload);
2010-02-12 19:24:25 +00:00
return 1 ; #response mismatch
}
2012-11-04 17:25:38 +00:00
if ( $ self - > { hasretried } ) { #if we sent this out multiple times, mark the sequence as taboo
$ self - > { hasretried } = 0 ;
$ tabooseq { $ self - > { expectednetfn } } - > { $ self - > { expectedcmd } } - > { $ self - > { seqlun } } = 16 ; #consider a lun taboo for 16 overflow cycles
}
2012-11-04 17:25:25 +00:00
#set to impossible values to reflect the fact we expect *no* command/nnetfn at the moment
$ self - > { expectednetfn } = 0x1ff ;
$ self - > { expectedcmd } = 0x1ff ;
2012-09-13 21:00:07 +00:00
$ self - > { seqlun } += 4 ; #increment by 1<<2
2012-10-11 21:02:53 +00:00
if ( $ self - > { seqlun } > 0xff ) { #overflow case
2012-10-25 19:26:09 +00:00
#Problem with rqaddr iteration strategy to get more sequence numbers, changing swid invalidates reservation ids for some BMCs...
# if ($self->{rqaddr} == 0x8d) { #rqaddr itself is forced to overflow
# $self->{rqaddr}=0x81;
# } else {
# $self->{rqaddr}+=2; #table 5-4 demands rqaddr be odd for software ids, so we must increment by 2
#}
2012-10-11 21:02:53 +00:00
$ self - > { seqlun } & = 0xff ; #keep it one byte
}
2010-02-12 19:24:25 +00:00
delete $ sessions_waiting { $ self } ; #deregister self as satisfied, callback will reregister if appropriate
2012-09-16 03:53:43 +00:00
delete $ self - > { pendingargs } ;
2010-01-27 14:58:18 +00:00
splice @ payload , 0 , 5 ; #remove rsaddr/netfs/lun/checksum/rq/seq/lun
pop @ payload ; #remove checksum
my $ rsp ;
$ rsp - > { cmd } = shift @ payload ;
$ rsp - > { code } = shift @ payload ;
$ rsp - > { data } = \ @ payload ;
2012-11-05 14:36:04 +00:00
$ self - > { timeout } = $ initialtimeout + ( 0.5 * rand ( ) ) ;
2010-01-27 14:58:18 +00:00
$ self - > { ipmicallback } - > ( $ rsp , $ self - > { ipmicallback_args } ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-27 14:58:18 +00:00
}
sub ipmi15authcode {
my $ self = shift ;
#per table 22-22 'authcode algorithms'
my @ data = @ _ ;
my @ password ;
my @ code ;
if ( $ self - > { passbytes } ) {
@ password = @ { $ self - > { passbytes } } ;
} else {
@ password = unpack ( "C*" , $ self - > { password } ) ;
for ( my $ i = scalar @ password ; $ i < 16 ; $ i + + ) {
$ password [ $ i ] = 0 ;
}
$ self - > { passbytes } = \ @ password ;
}
my @ sequencebytes = @ { $ self - > { sequencenumberbytes } } ;
if ( $ self - > { checkremotecode } ) {
@ sequencebytes = @ { $ self - > { remotesequencebytes } } ;
}
if ( $ self - > { authtype } == 0 ) {
return ( ) ;
} elsif ( $ self - > { authtype } == 2 ) {
return unpack ( "C*" , md5 ( pack ( "C*" , @ password , @ { $ self - > { sessionid } } , @ data , @ sequencebytes , @ password ) ) ) ; #ignoring single-session channels
}
#Not supporting plaintext passwords, that would be asinine
}
#this function accepts a generic ipmi command and applies current session data and handles the 1.5<->2.0 differences
sub sendpayload {
#implementation used section 13.6, examle ipmi over lan packet
my $ self = shift ;
my % args = @ _ ;
my @ msg = ( 0x6 , 0x0 , 0xff , 0x07 ) ; #RMCP header is constant in IPMI
2010-01-28 22:06:22 +00:00
my $ type = $ args { type } & 0b00111111 ;
2010-01-27 14:58:18 +00:00
my @ payload = @ { $ args { payload } } ;
2010-02-05 18:11:12 +00:00
$ self - > { pendingargs } = \ % args ;
2012-02-27 17:49:29 +00:00
2010-01-27 14:58:18 +00:00
push @ msg , $ self - > { 'authtype' } ; # add authtype byte (will support 0 only for session establishment, 2 for ipmi 1.5, 6 for ipmi2
if ( $ self - > { 'ipmiversion' } eq '2.0' ) { #TODO: revisit this to see if assembly makes sense
push @ msg , $ args { type } ;
2010-01-28 22:06:22 +00:00
if ( $ type == 2 ) {
2010-01-27 14:58:18 +00:00
push @ msg , @ { $ self - > { 'iana' } } , 0 ;
push @ msg , @ { $ self - > { 'oem_payload_id' } } ;
}
push @ msg , @ { $ self - > { sessionid } } ;
}
push @ msg , @ { $ self - > { sequencenumberbytes } } ;
if ( $ self - > { 'ipmiversion' } eq '1.5' ) { #ipmi 2.0 for some reason swapped session id and seq number location
push @ msg , @ { $ self - > { sessionid } } ;
unless ( $ self - > { authtype } == 0 ) {
push @ msg , $ self - > ipmi15authcode ( @ payload ) ;
}
push @ msg , scalar ( @ payload ) ;
push @ msg , @ payload ;
#TODO: sweat a pad or not? spec isn't crystal clear on the 'legacy pad' and it sounds like it is just for some old crappy nics that have no business in a good server
} elsif ( $ self - > { 'ipmiversion' } eq '2.0' ) {
#TODO:
2010-01-28 03:42:24 +00:00
my $ size = scalar ( @ payload ) ;
2010-01-29 04:15:52 +00:00
if ( $ self - > { confalgo } ) {
my $ pad = ( $ size + 1 ) % 16 ;
if ( $ pad ) { $ pad = 16 - $ pad ; }
my $ newsize = $ size + $ pad + 17 ;
push @ msg , ( $ newsize & 0xff , $ newsize >> 8 ) ;
my @ iv ;
2010-01-29 04:40:10 +00:00
foreach ( 1 .. 16 ) { #generate a new iv for outbound packet
2010-01-29 04:16:34 +00:00
my $ num = int ( rand ( 255 ) ) ;
2010-01-29 04:15:52 +00:00
push @ msg , $ num ;
push @ iv , $ num ;
}
2010-01-30 01:06:16 +00:00
my $ cipher = Crypt::CBC - > new ( - literal_key = > 1 , - key = > $ self - > { aeskey } , - cipher = > "Rijndael" , - header = > "none" , - iv = > pack ( "C*" , @ iv ) , - keysize = > 16 , - padding = > \ & cbc_pad ) ;
push @ msg , ( unpack ( "C*" , $ cipher - > encrypt ( pack ( "C*" , @ payload ) ) ) ) ;
2010-01-29 04:15:52 +00:00
} else {
push @ msg , ( $ size & 0xff , $ size >> 8 ) ;
push @ msg , @ payload ;
}
2010-01-28 22:06:22 +00:00
if ( $ self - > { integrityalgo } ) {
2010-01-29 02:32:47 +00:00
my @ integdata = @ msg [ 4 .. ( scalar @ msg ) - 1 ] ;
my $ neededpad = ( ( scalar @ integdata ) + 2 ) % 4 ;
if ( $ neededpad ) { $ neededpad = 4 - $ neededpad ; }
for ( my $ i = 0 ; $ i < $ neededpad ; $ i + + ) {
push @ integdata , 0xff ;
push @ msg , 0xff ;
}
push @ msg , $ neededpad ;
push @ integdata , $ neededpad ;
push @ msg , 7 ;
push @ integdata , 7 ;
my $ intdata = pack ( "C*" , @ integdata ) ;
my @ acode = unpack ( "C*" , hmac_sha1 ( $ intdata , $ self - > { k1 } ) ) ;
push @ msg , splice @ acode , 0 , 12 ;
2010-01-27 14:58:18 +00:00
#push integrity pad
2010-01-28 03:42:24 +00:00
#push @msg,0x7; #reserved byte in 2.0
2010-01-27 14:58:18 +00:00
#push integrity data
2010-01-28 22:06:22 +00:00
}
2010-01-27 14:58:18 +00:00
}
2012-09-15 00:57:44 +00:00
unless ( $ args { nowait } or $ self - > { nowait } ) { #if nowait indicated, the packet will be sent regardless of maxpending
2012-08-08 19:52:02 +00:00
#primary use case would be retries that should represent no delta to pending sessions in aggregate and therefore couldn't exceed maxpending anywy
#if we did do this on timedout, waitforrsp may recurse, which is a complicated issue. Theoretically, if waitforrsp protected itself, it
#would act the same, but best be explicit about nowait if practice does not match theory
#another scenario is if we have urgent payload for a BMC (PET acknowledge, negotiating login if temp session id is very short lived
$ self - > waitforrsp ( timeout = > 0 ) ; #the intent here is to interrupt outgoing activity to give a chance to respond to incoming data
#until we send, the ball is in our court so things are less time critical
while ( $ pendingpackets > $ maxpending ) { #if we hit our ceiling, wait until a slot frees up, which can't happen until either a packet is received or someone gives up
$ self - > waitforrsp ( ) ;
}
2011-01-10 20:25:35 +00:00
}
2010-01-27 14:58:18 +00:00
$ socket - > send ( pack ( "C*" , @ msg ) , 0 , $ self - > { peeraddr } ) ;
2012-07-25 20:19:08 +00:00
$ sessions_waiting { $ self } = { } ;
$ sessions_waiting { $ self } - > { ipmisession } = $ self ;
if ( $ args { delayxmit } ) {
$ sessions_waiting { $ self } - > { timeout } = time ( ) + $ args { delayxmit } ;
2012-11-05 14:36:04 +00:00
$ self - > { timeout } = ( $ initialtimeout + ( 0.5 * rand ( ) ) ) / 2 ; #since we are burning one of the retry attempts, start the backoff algorithm faster to make it come out even
2012-07-25 20:19:08 +00:00
undef $ args { delayxmit } ;
return ; #don't actually transmit packet, use retry timer to start us off
} else {
$ sessions_waiting { $ self } - > { timeout } = time ( ) + $ self - > { timeout } ;
}
2011-01-10 20:25:35 +00:00
$ pendingpackets += 1 ;
2010-01-27 14:58:18 +00:00
if ( $ self - > { sequencenumber } ) { #if using non-zero, increment, otherwise..
$ self - > { sequencenumber } += 1 ;
$ self - > { sequencenumberbytes } = [ $ self - > { sequencenumber } & 0xff , ( $ self - > { sequencenumber } >> 8 ) & 0xff , ( $ self - > { sequencenumber } >> 16 ) & 0xff , ( $ self - > { sequencenumber } >> 24 ) & 0xff ] ;
}
}
1 ;