From 45a8facadd24e3fbd88aebdfb20710e7b788df96 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 11 Jul 2017 16:55:50 -0400 Subject: [PATCH] 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 --- pyghmi/ipmi/private/serversession.py | 8 ++- pyghmi/ipmi/private/session.py | 92 +++++++++++++++++----------- 2 files changed, 62 insertions(+), 38 deletions(-) diff --git a/pyghmi/ipmi/private/serversession.py b/pyghmi/ipmi/private/serversession.py index ee8cea69..06bcbddb 100644 --- a/pyghmi/ipmi/private/serversession.py +++ b/pyghmi/ipmi/private/serversession.py @@ -78,7 +78,10 @@ class ServerSession(ipmisession.Session): self.sockaddr = clientaddr self.pendingpayloads = 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)) self.send_payload(response, constants.payload_types['rmcpplusopenresponse'], @@ -267,10 +270,11 @@ class IpmiServer(object): authstatus, chancap, *oemdata) self.kg = None self.timeout = 60 + self.port = port addrinfo = socket.getaddrinfo(address, port, 0, socket.SOCK_DGRAM)[0] 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, sockaddr): diff --git a/pyghmi/ipmi/private/session.py b/pyghmi/ipmi/private/session.py index 721fdacc..9544bb3d 100644 --- a/pyghmi/ipmi/private/session.py +++ b/pyghmi/ipmi/private/session.py @@ -196,16 +196,18 @@ def _io_graball(mysockets, iowaiters): # into the select if len(rdata[0]) < 4: continue + myport = mysocket.getsockname()[1] rdata = rdata + (mysocket,) 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 rdata = rdata + (True,) - relsession = Session.bmc_handlers[rdata[1]] + relsession = Session.bmc_handlers[rdata[1]][myport] elif rdata[2] in Session.bmc_handlers: # pyghmi is the bmc, and we have sessionless data rdata = rdata + (False,) - relsession = Session.bmc_handlers[rdata[2]] + relsession = Session.bmc_handlers[rdata[2]][0] if relsession is not None: relsession.pktqueue.append(rdata) sessionqueue.append(relsession) @@ -302,12 +304,13 @@ class Session(object): @classmethod def _cleanup(cls): for sesskey in list(cls.bmc_handlers): - session = cls.bmc_handlers[sesskey] - session.cleaningup = True - session.logout() + for portent in list(cls.bmc_handlers[sesskey]): + session = cls.bmc_handlers[sesskey][portent] + session.cleaningup = True + session.logout() @classmethod - def _assignsocket(cls, server=None): + def _assignsocket(cls, server=None, forbiddensockets=()): global iothread global iothreadready global iosockets @@ -321,9 +324,15 @@ class Session(object): if server is None: sorted_candidates = sorted(dictitems(cls.socketpool), key=operator.itemgetter(1)) - if sorted_candidates and sorted_candidates[0][1] < MAX_BMCS_PER_SOCKET: - cls.socketpool[sorted_candidates[0][0]] += 1 - return sorted_candidates[0][0] + if sorted_candidates is None: + sorted_candidates = [] + 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 if server: # Regardless of whether ipv6 is supported or not, we @@ -403,6 +412,7 @@ class Session(object): kg=None, onlogon=None): trueself = None + forbidsock = [] for res in socket.getaddrinfo(bmc, port, 0, socket.SOCK_DGRAM): sockaddr = res[4] if ipv6support and res[0] == socket.AF_INET: @@ -410,17 +420,29 @@ class Session(object): newhost = '::ffff:' + sockaddr[0] sockaddr = (newhost, sockaddr[1], 0, 0) if sockaddr in cls.bmc_handlers: - self = cls.bmc_handlers[sockaddr] - if (self.bmc == bmc and self.userid == userid and - self.password == password and self.kgo == kg and - (self.logged or self.logging) and - cls._is_session_valid(self)): - trueself = self - else: - del cls.bmc_handlers[sockaddr] + for portself in list(dictitems(cls.bmc_handlers[sockaddr])): + self = portself[1] + if not ((self.logged or self.logging) and + cls._is_session_valid(self)): + # we have encountered a leftover broken session + del cls.bmc_handlers[sockaddr][portself[0]] + continue + 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: return trueself - return object.__new__(cls) + self = object.__new__(cls) + self.forbidsock = forbidsock + return self def __init__(self, bmc, @@ -486,7 +508,7 @@ class Session(object): if self.__class__.socketchecking is None: self.__class__.socketchecking = threading.Lock() with self.socketchecking: - self.socket = self._assignsocket() + self.socket = self._assignsocket(forbiddensockets=self.forbidsock) self.login() if not self.async: while self.logging: @@ -522,9 +544,13 @@ class Session(object): # since this session is broken, remove it from the handler list # This allows constructor to create a new, functional object to # replace this one + myport = self.socket.getsockname()[1] for sockaddr in self.allsockaddrs: - if sockaddr in Session.bmc_handlers: - del Session.bmc_handlers[sockaddr] + if (sockaddr in Session.bmc_handlers and + 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: self.broken = True self.socketpool[self.socket] -= 1 @@ -1200,23 +1226,14 @@ class Session(object): pkt[0] = bytearray(pkt[0]) if not (pkt[0][0] == 6 and pkt[0][2:4] == b'\xff\x07'): 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: self._handle_ipmi_packet(pkt[0], sockaddr=pkt[1], qent=pkt) elif pkt[2] in self.bmc_handlers: 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): if self.sockaddr is None and sockaddr is not None: self.sockaddr = sockaddr @@ -1235,7 +1252,7 @@ class Session(object): # still active UDP source port. Clear ourselves out and punt # to IpmiServer del Session.bmc_handlers[sockaddr] - iserver = Session.bmc_handlers[qent[2]] + iserver = Session.bmc_handlers[qent[2]][0] iserver.pktqueue.append(qent) iserver.process_pktqueue() return @@ -1657,6 +1674,7 @@ class Session(object): # he have not yet picked a working sockaddr for this connection, # try all the candidates that getaddrinfo provides self.allsockaddrs = [] + myport = self.socket.getsockname()[1] try: for res in socket.getaddrinfo(self.bmc, self.port, @@ -1668,7 +1686,9 @@ class Session(object): newhost = '::ffff:' + sockaddr[0] sockaddr = (newhost, sockaddr[1], 0, 0) 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) except socket.gaierror: raise exc.IpmiException(