Allow things like multiple distinct users

A new set of credentials would break existing sessions.
Support concurrent use of multiple users/passwords, since
there are valid use cases.

Change-Id: Ic63364f05ec9270e06656d13bbb02d7c88fd21d4
This commit is contained in:
Jarrod Johnson 2017-07-11 16:55:50 -04:00
parent b423b68233
commit 45a8facadd
2 changed files with 62 additions and 38 deletions

View File

@ -78,7 +78,10 @@ class ServerSession(ipmisession.Session):
self.sockaddr = clientaddr self.sockaddr = clientaddr
self.pendingpayloads = collections.deque([]) self.pendingpayloads = collections.deque([])
self.pktqueue = collections.deque([]) self.pktqueue = collections.deque([])
ipmisession.Session.bmc_handlers[clientaddr] = self if clientaddr not in ipmisession.Session.bmc_handlers:
ipmisession.Session.bmc_handlers[clientaddr] = {bmc.port: self}
else:
ipmisession.Session.bmc_handlers[clientaddr][bmc.port] = self
response = self.create_open_session_response(bytearray(request)) response = self.create_open_session_response(bytearray(request))
self.send_payload(response, self.send_payload(response,
constants.payload_types['rmcpplusopenresponse'], constants.payload_types['rmcpplusopenresponse'],
@ -267,10 +270,11 @@ class IpmiServer(object):
authstatus, chancap, *oemdata) authstatus, chancap, *oemdata)
self.kg = None self.kg = None
self.timeout = 60 self.timeout = 60
self.port = port
addrinfo = socket.getaddrinfo(address, port, 0, addrinfo = socket.getaddrinfo(address, port, 0,
socket.SOCK_DGRAM)[0] socket.SOCK_DGRAM)[0]
self.serversocket = ipmisession.Session._assignsocket(addrinfo) self.serversocket = ipmisession.Session._assignsocket(addrinfo)
ipmisession.Session.bmc_handlers[self.serversocket] = self ipmisession.Session.bmc_handlers[self.serversocket] = {0: self}
def send_auth_cap(self, myaddr, mylun, clientaddr, clientlun, clientseq, def send_auth_cap(self, myaddr, mylun, clientaddr, clientlun, clientseq,
sockaddr): sockaddr):

View File

@ -196,16 +196,18 @@ def _io_graball(mysockets, iowaiters):
# into the select # into the select
if len(rdata[0]) < 4: if len(rdata[0]) < 4:
continue continue
myport = mysocket.getsockname()[1]
rdata = rdata + (mysocket,) rdata = rdata + (mysocket,)
relsession = None relsession = None
if rdata[1] in Session.bmc_handlers: if (rdata[1] in Session.bmc_handlers and
myport in Session.bmc_handlers[rdata[1]]):
# session data # session data
rdata = rdata + (True,) rdata = rdata + (True,)
relsession = Session.bmc_handlers[rdata[1]] relsession = Session.bmc_handlers[rdata[1]][myport]
elif rdata[2] in Session.bmc_handlers: elif rdata[2] in Session.bmc_handlers:
# pyghmi is the bmc, and we have sessionless data # pyghmi is the bmc, and we have sessionless data
rdata = rdata + (False,) rdata = rdata + (False,)
relsession = Session.bmc_handlers[rdata[2]] relsession = Session.bmc_handlers[rdata[2]][0]
if relsession is not None: if relsession is not None:
relsession.pktqueue.append(rdata) relsession.pktqueue.append(rdata)
sessionqueue.append(relsession) sessionqueue.append(relsession)
@ -302,12 +304,13 @@ class Session(object):
@classmethod @classmethod
def _cleanup(cls): def _cleanup(cls):
for sesskey in list(cls.bmc_handlers): for sesskey in list(cls.bmc_handlers):
session = cls.bmc_handlers[sesskey] for portent in list(cls.bmc_handlers[sesskey]):
session.cleaningup = True session = cls.bmc_handlers[sesskey][portent]
session.logout() session.cleaningup = True
session.logout()
@classmethod @classmethod
def _assignsocket(cls, server=None): def _assignsocket(cls, server=None, forbiddensockets=()):
global iothread global iothread
global iothreadready global iothreadready
global iosockets global iosockets
@ -321,9 +324,15 @@ class Session(object):
if server is None: if server is None:
sorted_candidates = sorted(dictitems(cls.socketpool), sorted_candidates = sorted(dictitems(cls.socketpool),
key=operator.itemgetter(1)) key=operator.itemgetter(1))
if sorted_candidates and sorted_candidates[0][1] < MAX_BMCS_PER_SOCKET: if sorted_candidates is None:
cls.socketpool[sorted_candidates[0][0]] += 1 sorted_candidates = []
return sorted_candidates[0][0] for candidate in sorted_candidates:
if candidate[1] >= MAX_BMCS_PER_SOCKET:
break
if candidate[0] in forbiddensockets:
continue
cls.socketpool[candidate[0]] += 1
return candidate[0]
# we need a new socket # we need a new socket
if server: if server:
# Regardless of whether ipv6 is supported or not, we # Regardless of whether ipv6 is supported or not, we
@ -403,6 +412,7 @@ class Session(object):
kg=None, kg=None,
onlogon=None): onlogon=None):
trueself = None trueself = None
forbidsock = []
for res in socket.getaddrinfo(bmc, port, 0, socket.SOCK_DGRAM): for res in socket.getaddrinfo(bmc, port, 0, socket.SOCK_DGRAM):
sockaddr = res[4] sockaddr = res[4]
if ipv6support and res[0] == socket.AF_INET: if ipv6support and res[0] == socket.AF_INET:
@ -410,17 +420,29 @@ class Session(object):
newhost = '::ffff:' + sockaddr[0] newhost = '::ffff:' + sockaddr[0]
sockaddr = (newhost, sockaddr[1], 0, 0) sockaddr = (newhost, sockaddr[1], 0, 0)
if sockaddr in cls.bmc_handlers: if sockaddr in cls.bmc_handlers:
self = cls.bmc_handlers[sockaddr] for portself in list(dictitems(cls.bmc_handlers[sockaddr])):
if (self.bmc == bmc and self.userid == userid and self = portself[1]
self.password == password and self.kgo == kg and if not ((self.logged or self.logging) and
(self.logged or self.logging) and cls._is_session_valid(self)):
cls._is_session_valid(self)): # we have encountered a leftover broken session
trueself = self del cls.bmc_handlers[sockaddr][portself[0]]
else: continue
del cls.bmc_handlers[sockaddr] if (self.bmc == bmc and self.userid == userid and
self.password == password and self.kgo == kg):
trueself = self
break
# ok, the candidate seems to be working, but does not match
# will need to allow creation of a new session, but
# must forbid use of this socket so that the socket
# share routing code does not get confused.
# in principle, should be able to distinguish by session
# id, however it's easier this way
forbidsock.append(self.socket)
if trueself: if trueself:
return trueself return trueself
return object.__new__(cls) self = object.__new__(cls)
self.forbidsock = forbidsock
return self
def __init__(self, def __init__(self,
bmc, bmc,
@ -486,7 +508,7 @@ class Session(object):
if self.__class__.socketchecking is None: if self.__class__.socketchecking is None:
self.__class__.socketchecking = threading.Lock() self.__class__.socketchecking = threading.Lock()
with self.socketchecking: with self.socketchecking:
self.socket = self._assignsocket() self.socket = self._assignsocket(forbiddensockets=self.forbidsock)
self.login() self.login()
if not self.async: if not self.async:
while self.logging: while self.logging:
@ -522,9 +544,13 @@ class Session(object):
# since this session is broken, remove it from the handler list # since this session is broken, remove it from the handler list
# This allows constructor to create a new, functional object to # This allows constructor to create a new, functional object to
# replace this one # replace this one
myport = self.socket.getsockname()[1]
for sockaddr in self.allsockaddrs: for sockaddr in self.allsockaddrs:
if sockaddr in Session.bmc_handlers: if (sockaddr in Session.bmc_handlers and
del Session.bmc_handlers[sockaddr] myport in Session.bmc_hansdlers[sockaddr]):
del Session.bmc_handlers[sockaddr][myport]
if Session.bmc_handlers[sockaddr] == {}:
del Session.bmc_handlers[sockaddr]
elif not self.broken: elif not self.broken:
self.broken = True self.broken = True
self.socketpool[self.socket] -= 1 self.socketpool[self.socket] -= 1
@ -1200,23 +1226,14 @@ class Session(object):
pkt[0] = bytearray(pkt[0]) pkt[0] = bytearray(pkt[0])
if not (pkt[0][0] == 6 and pkt[0][2:4] == b'\xff\x07'): if not (pkt[0][0] == 6 and pkt[0][2:4] == b'\xff\x07'):
continue continue
# this should be in specific context, no need to check port
# since recvfrom result was already routed to this object
# specifically
if pkt[1] in self.bmc_handlers: if pkt[1] in self.bmc_handlers:
self._handle_ipmi_packet(pkt[0], sockaddr=pkt[1], qent=pkt) self._handle_ipmi_packet(pkt[0], sockaddr=pkt[1], qent=pkt)
elif pkt[2] in self.bmc_handlers: elif pkt[2] in self.bmc_handlers:
self.sessionless_data(pkt[0], pkt[1]) self.sessionless_data(pkt[0], pkt[1])
@classmethod
def _route_ipmiresponse(cls, sockaddr, data, mysocket):
if not (data[0] == '\x06' and data[2:4] == '\xff\x07'): # not ipmi
return
try:
cls.bmc_handlers[sockaddr]._handle_ipmi_packet(data,
sockaddr=sockaddr)
except KeyError:
# check if we have a server attached to the target socket
if mysocket in cls.bmc_handlers:
cls.bmc_handlers[mysocket].sessionless_data(data, sockaddr)
def _handle_ipmi_packet(self, data, sockaddr=None, qent=None): def _handle_ipmi_packet(self, data, sockaddr=None, qent=None):
if self.sockaddr is None and sockaddr is not None: if self.sockaddr is None and sockaddr is not None:
self.sockaddr = sockaddr self.sockaddr = sockaddr
@ -1235,7 +1252,7 @@ class Session(object):
# still active UDP source port. Clear ourselves out and punt # still active UDP source port. Clear ourselves out and punt
# to IpmiServer # to IpmiServer
del Session.bmc_handlers[sockaddr] del Session.bmc_handlers[sockaddr]
iserver = Session.bmc_handlers[qent[2]] iserver = Session.bmc_handlers[qent[2]][0]
iserver.pktqueue.append(qent) iserver.pktqueue.append(qent)
iserver.process_pktqueue() iserver.process_pktqueue()
return return
@ -1657,6 +1674,7 @@ class Session(object):
# he have not yet picked a working sockaddr for this connection, # he have not yet picked a working sockaddr for this connection,
# try all the candidates that getaddrinfo provides # try all the candidates that getaddrinfo provides
self.allsockaddrs = [] self.allsockaddrs = []
myport = self.socket.getsockname()[1]
try: try:
for res in socket.getaddrinfo(self.bmc, for res in socket.getaddrinfo(self.bmc,
self.port, self.port,
@ -1668,7 +1686,9 @@ class Session(object):
newhost = '::ffff:' + sockaddr[0] newhost = '::ffff:' + sockaddr[0]
sockaddr = (newhost, sockaddr[1], 0, 0) sockaddr = (newhost, sockaddr[1], 0, 0)
self.allsockaddrs.append(sockaddr) self.allsockaddrs.append(sockaddr)
Session.bmc_handlers[sockaddr] = self if sockaddr not in Session.bmc_handlers:
Session.bmc_handlers[sockaddr] = {}
Session.bmc_handlers[sockaddr][myport] = self
_io_sendto(self.socket, self.netpacket, sockaddr) _io_sendto(self.socket, self.netpacket, sockaddr)
except socket.gaierror: except socket.gaierror:
raise exc.IpmiException( raise exc.IpmiException(