2
0
mirror of https://opendev.org/x/pyghmi synced 2025-04-14 17:19:29 +00:00

More style reworking

This commit is contained in:
Jarrod Johnson 2013-06-25 11:44:57 -04:00
parent ab1b981547
commit 1dde7b8a70

View File

@ -69,7 +69,8 @@ def get_ipmi_error(response,suffix=""):
netfn = response['netfn']
if code == 0:
return False
if (netfn,command) in command_completion_codes and code in command_completion_codes[(netfn,command)]:
if ((netfn,command) in command_completion_codes and
code in command_completion_codes[(netfn,command)]):
return command_completion_codes[(netfn,command)][code]+suffix
elif code in ipmi_completion_codes:
return ipmi_completion_codes[code]+suffix
@ -90,15 +91,19 @@ class ipmi_session:
@classmethod
def _createsocket(cls):
atexit.register(cls._cleanup)
cls.socket = socket.socket(socket.AF_INET6,socket.SOCK_DGRAM) #INET6 can do IPv4 if you are nice to it
try: #we will try to fixup our receive buffer size if we are smaller than allowed.
cls.socket = socket.socket(socket.AF_INET6,socket.SOCK_DGRAM) #INET6
#can do IPv4 if you are nice to it
try: #we will try to fixup our receive buffer size if we are smaller
#than allowed.
maxmf = open("/proc/sys/net/core/rmem_max")
rmemmax = int(maxmf.read())
rmemmax = rmemmax/2
curmax=cls.socket.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)
curmax = curmax/2
if (rmemmax > curmax):
cls.socket.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF,rmemmax)
cls.socket.setsockopt(socket.SOL_SOCKET,
socket.SO_RCVBUF,
rmemmax)
except:
pass
curmax=cls.socket.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)
@ -155,7 +160,7 @@ class ipmi_session:
while not self.logged:
ipmi_session.wait_for_rsp()
def _initsession(self):
self.localsid=2017673555 #this number can be whatever we want. I picked
self.localsid=2017673555 #this number can be whatever we want. I picked
#'xCAT' minus 1 so that a hexdump of packet
# would show xCAT
self.privlevel=4 #for the moment, assume admin access
@ -195,7 +200,8 @@ class ipmi_session:
return csum
'''
This function generates the core ipmi payload that would be applicable for any channel (including KCS)
This function generates the core ipmi payload that would be applicable
for any channel (including KCS)
'''
def _make_ipmi_payload(self,netfn,command,data=()):
self.expectedcmd=command
@ -276,44 +282,57 @@ class ipmi_session:
message += self._ipmi15authcode(payload)
message.append(len(payload))
message += payload
totlen=34+len(message) #Guessing the ipmi spec means the whole packet ande assume no tag in old 1.5 world
totlen=34+len(message) #Guessing the ipmi spec means the whole
#packet and assume no tag in old 1.5 world
if (totlen in (56,84,112,128,156)):
message.append(0) #Legacy pad as mandated by ipmi spec
elif self.ipmiversion == 2.0:
psize = len(payload)
if self.confalgo:
pad = (psize+1)%16 #pad has to account for one byte field as in the _aespad function
pad = (psize+1)%16 #pad has to account for one byte field as in
#the _aespad function
if pad: #if no pad needed, then we take no more action
pad = 16-pad
newpsize=psize+pad+17 #new payload size grew according to pad size, plus pad length, plus 16 byte IV (Table 13-20)
newpsize=psize+pad+17 #new payload size grew according to pad
#size, plus pad length, plus 16 byte IV
#(Table 13-20)
message.append(newpsize&0xff)
message.append(newpsize>>8);
iv=os.urandom(16)
message += list(unpack("16B",iv))
payloadtocrypt=_aespad(payload)
crypter = AES.new(self.aeskey,AES.MODE_CBC,iv)
crypted = crypter.encrypt(pack("%dB"%len(payloadtocrypt),*payloadtocrypt))
crypted = crypter.encrypt(pack("%dB"%len(payloadtocrypt),
*payloadtocrypt))
crypted = list(unpack("%dB"%len(crypted),crypted))
message += crypted
else: #no confidetiality algorithm
message.append(psize&0xff)
message.append(psize>>8);
message += list(payload)
if self.integrityalgo: #see table 13-8, RMCP+ packet format TODO(jbjohnso): SHA256 which is now allowed
if self.integrityalgo: #see table 13-8,
#RMCP+ packet format
#TODO(jbjohnso): SHA256 which is now allowed
neededpad=(len(message)-2)%4
if neededpad:
neededpad = 4-neededpad
message += [0xff]*neededpad
message.append(neededpad)
message.append(7) #reserved, 7 is the required value for the specification followed
message.append(7) #reserved, 7 is the required value for the
#specification followed
integdata = message[4:]
authcode = HMAC.new(self.k1,pack("%dB"%len(integdata),*integdata),SHA).digest()[:12] #SHA1-96 per RFC2404 truncates to 96 bits
authcode = HMAC.new(self.k1,
pack("%dB"%len(integdata),
*integdata),
SHA).digest()[:12] #SHA1-96
#per RFC2404 truncates to 96 bits
message += unpack("12B",authcode)
self.netpacket = pack("!%dB"%len(message),*message)
self._xmit_packet()
def _ipmi15authcode(self,payload,checkremotecode=False):
if self.authtype == 0: #Only for things prior to auth in ipmi 1.5, not like 2.0 cipher suite 0
if self.authtype == 0: #Only for things prior to auth in ipmi 1.5, not
#like 2.0 cipher suite 0
return ()
password = self.password
padneeded = 16 - len(password)
@ -335,16 +354,21 @@ class ipmi_session:
if 'error' in response:
call_with_optional_args(self.onlogon,response,self.onlogonargs)
return
if response['code'] == 0xcc and self.ipmi15only is not None: #tried ipmi 2.0 against a 1.5 which should work, but some bmcs thought 'reserved' meant 'must be zero'
if response['code'] == 0xcc and self.ipmi15only is not None:
#tried ipmi 2.0 against a 1.5 which should work, but some bmcs
#thought 'reserved' meant 'must be zero'
self.ipmi15only=1
return self._get_channel_auth_cap()
errstr = get_ipmi_error(response,suffix=" while trying to get channel authentication capabalities")
mysuffix=" while trying to get channel authentication capabalities")
errstr = get_ipmi_error(response,suffix=mysuffix)
if errstr:
call_with_optional_args(self.onlogon,{'error': errstr},self.onlogonargs)
call_with_optional_args(self.onlogon,
{'error': errstr},
self.onlogonargs)
return
data = response['data']
self.currentchannel=data[0]
if data[1] & 0b10000000 and data[3] & 0b10: #those two bits together indicate ipmi 2.0 support
if data[1] & 0b10000000 and data[3] & 0b10: #ipmi 2.0 support
self.ipmiversion=2.0
if self.ipmiversion == 1.5:
if not (data[1] & 0b100):
@ -367,7 +391,8 @@ class ipmi_session:
This sends the activate session payload. We pick '1' as the requested sequence number without perturbing our real sequence number
'''
def _activate_session(self,data):
rqdata = [2,4]+list(data)+[1,0,0,0]; #TODO(jbjohnso): this always requests admin level, this could be toned down, but maybe 2.0 is the answer
rqdata = [2,4]+list(data)+[1,0,0,0];
#TODO(jbjohnso): this always requests admin level (1.5)
self.ipmicallback=self._activated_session
self._send_ipmi_net_payload(netfn=0x6,command=0x3a,data=rqdata)
@ -485,11 +510,14 @@ class ipmi_session:
return -5 # remote sequence number is too low, reject it
self.remsequencenumber=remsequencenumber
if ord(data[4]) != self.authtype:
return -2 #BMC responded with mismatch authtype, for the sake of mutual authentication reject it. If this causes legitimate issues, it's the vendor's fault
return -2 #BMC responded with mismatch authtype, for the sake of
#mutual authentication reject it. If this causes
#legitimate issues, it's the vendor's fault
remsessid = unpack("<I",data[9:13])[0]
if remsessid != self.sessionid:
return -1 #does not match our session id, drop it
#now we need a mutable representation of the packet, rather than copying pieces of the packet over and over
#now we need a mutable representation of the packet, rather than
#copying pieces of the packet over and over
rsp=list(unpack("!%dB"%len(data),data))
authcode=False
if data[4] == '\x02': # we have an authcode in this ipmi 1.5 packet...
@ -497,8 +525,10 @@ class ipmi_session:
del rsp[13:29] #this is why we needed a mutable representation
payload=list(rsp[14:14+rsp[13]])
if authcode:
expectedauthcode=self._ipmi15authcode(payload,checkremotecode=True)
expectedauthcode=pack("%dB"%len(expectedauthcode),*expectedauthcode)
expectedauthcode=self._ipmi15authcode(payload,
checkremotecode=True)
expectedauthcode=pack("%dB"%len(expectedauthcode),
*expectedauthcode)
if expectedauthcode != authcode:
return
self._parse_ipmi_payload(payload)
@ -638,9 +668,12 @@ class ipmi_session:
if self.sessioncontext != "EXPECTINGRAKP4" or data[0] != self.rmcptag:
return -9
if data[1] != 0:
if data[1] == 2 and self.logontries: #if we retried RAKP3 because RAKP4 got dropped, BMC can consider it done and we must restart
if data[1] == 2 and self.logontries: #if we retried RAKP3 because
#RAKP4 got dropped, BMC can consider it done and we must restart
self._relog()
if data[1] == 15 and self.logontries: #ignore 15 value if we are retrying. xCAT did but I can't recall why exactly
if data[1] == 15 and self.logontries: #ignore 15 value if we are
#retrying. xCAT did but I can't recall why exactly
#TODO(jbjohnso) jog my memory to update the comment
return
if data[1] in rmcp_codes:
errstr=rmcp_codes[data[1]]
@ -670,19 +703,24 @@ class ipmi_session:
Internal function to parse IPMI nugget once extracted from its framing
'''
def _parse_ipmi_payload(self,payload):
#For now, skip the checksums since we are in LAN only, TODO(jbjohnso): if implementing other channels, add checksum checks here
if (payload[4] != self.seqlun or payload[1]>>2 != self.expectednetfn or payload[5] != self.expectedcmd):
return -1 #this payload is not a match for our outstanding ipmi packet
#For now, skip the checksums since we are in LAN only,
#TODO(jbjohnso): if implementing other channels, add checksum checks
#here
if (payload[4] != self.seqlun or payload[1]>>2 != self.expectednetfn or
payload[5] != self.expectedcmd):
return -1 #this payload is not a match for our outstanding packet
if hasattr(self,'hasretried') and self.hasretried:
self.hasretried=0
self.tabooseq[(self.expectednetfn,self.expectedcmd,self.seqlun)]=16 # try to skip it for at most 16 cycles of overflow
self.tabooseq[(self.expectednetfn,self.expectedcmd,self.seqlun)]=16
#try to skip it for at most 16 cycles of overflow
#We want to now remember that we do not have an expected packet
self.expectednetfn=0x1ff #bigger than one byte means it can never match
self.expectedcmd=0x1ff
self.seqlun += 4 #prepare seqlun for next transmit
self.seqlun &= 0xff #when overflowing, wrap around
del ipmi_session.waiting_sessions[self]
self.lastpayload=None #render retry mechanism utterly incapable of doing anything, though it shouldn't matter
self.lastpayload=None #render retry mechanism utterly incapable of doing
#anything, though it shouldn't matter
self.last_payload_type=None
response={}
response['netfn'] = payload[1]>>2
@ -693,7 +731,9 @@ class ipmi_session:
del payload[0:2]
response['data']=payload
self.timeout=initialtimeout+(0.5*random())
call_with_optional_args(self.ipmicallback,response,self.ipmicallbackargs)
call_with_optional_args(self.ipmicallback,
response,
self.ipmicallbackargs)
def _timedout(self):
if not self.lastpayload:
@ -704,30 +744,41 @@ class ipmi_session:
return
if self.timeout > 5:
response={'error': 'timeout'}
call_with_optional_args(self.ipmicallback,response,self.ipmicallbackargs)
call_with_optional_args(self.ipmicallback,
response,
self.ipmicallbackargs)
self.nowait=False
return
elif self.sessioncontext == 'FAILED':
self.nowait=False
return
if self.sessioncontext == 'OPENSESSION':
#In this case, we want to craft a new session request to have unambiguous session id regardless of how packet was dropped or delayed
#in this case, it's safe to just redo the request
#In this case, we want to craft a new session request to have
#unambiguous session id regardless of how packet was dropped or
#delayed in this case, it's safe to just redo the request
self._open_rmcpplus_request()
elif self.sessioncontext == 'EXPECTINGRAKP2' or self.sessioncontext == 'EXPECTINGRAKP4':
#If we can't be sure which RAKP was dropped or that RAKP3/4 was just delayed, the most reliable thing to do is
#rewind and start over
#If we can't be sure which RAKP was dropped or that RAKP3/4 was just
#delayed, the most reliable thing to do is rewind and start over
#bmcs do not take kindly to receiving RAKP1 or RAKP3 twice
self._relog()
else: #in IPMI case, the only recourse is to act as if the packet is idempotent. SOL has more sophisticated retry handling
#the biggest risks are reset sp, which is often fruitless to retry and chassis reset, which sometimes will shoot itself systematically
#in the head in a shared port case making replies impossible
self.hasretried=1 #remember so that we can track taboo combinations of sequence number, netfn, and lun due to ambiguity on the wire
else: #in IPMI case, the only recourse is to act as if the packet is
#idempotent. SOL has more sophisticated retry handling
#the biggest risks are reset sp, which is often fruitless to retry
#and chassis reset, which sometimes will shoot itself
#systematically in the head in a shared port case making replies
#impossible
self.hasretried=1 #remember so that we can track taboo combinations
#of sequence number, netfn, and lun due to
#ambiguity on the wire
self._pack_payload()
self.nowait=False
def _xmit_packet(self):
if not self.nowait: #if we are retrying, we really need to get the packet out and get our timeout updated
ipmi_session.wait_for_rsp(timeout=0) #take a convenient opportunity to drain the socket queue if applicable
if not self.nowait: #if we are retrying, we really need to get the
#packet out and get our timeout updated
ipmi_session.wait_for_rsp(timeout=0) #take a convenient opportunity
#to drain the socket queue if
#applicable
while ipmi_session.pending > ipmi_session.maxpending:
ipmi_session.wait_for_rsp()
ipmi_session.waiting_sessions[self]={}
@ -736,15 +787,20 @@ class ipmi_session:
ipmi_session.pending+=1
if self.sockaddr:
ipmi_session.socket.sendto(self.netpacket,self.sockaddr)
else: #he have not yet picked a working sockaddr for this connection, try all the candidates that getaddrinfo provides
for res in socket.getaddrinfo(self.bmc,self.port,0,socket.SOCK_DGRAM):
else: #he have not yet picked a working sockaddr for this connection,
#try all the candidates that getaddrinfo provides
for res in socket.getaddrinfo(self.bmc,
self.port,
0,
socket.SOCK_DGRAM):
sockaddr = res[4]
if (res[0] == socket.AF_INET): #convert the sockaddr to AF_INET6
newhost='::ffff:'+sockaddr[0]
sockaddr = (newhost,sockaddr[1],0,0)
ipmi_session.bmc_handlers[sockaddr]=self
ipmi_session.socket.sendto(self.netpacket,sockaddr)
if self.sequencenumber: #seq number of zero will be left alone as it is special, otherwise increment
if self.sequencenumber: #seq number of zero will be left alone as it is
#special, otherwise increment
self.sequencenumber += 1
def logout(self,callback=None,callback_args=None):
if not self.logged:
@ -752,8 +808,13 @@ class ipmi_session:
return {'success': True }
callback({'success': True })
return
self.noretry=True #risk open sessions if logout request gets dropped, logout is not idempotent so this is the better bet
self.raw_command(command=0x3c,netfn=6,data=unpack("4B",pack("I",self.sessionid)),callback=callback,callback_args=callback_args)
self.noretry=True #risk open sessions if logout request gets dropped,
#logout is not idempotent so this is the better bet
self.raw_command(command=0x3c,
netfn=6,
data=unpack("4B",pack("I",self.sessionid)),
callback=callback,
callback_args=callback_args)
self.logged=0
if callback is None:
return {'success': True }