def process_chunk(self, data): """ Generic message dispatcher for IPA (sub)protocols based on protocol name, lambda default should never happen """ (_, proto, extension, content) = IPA().del_header(data) if content is not None: self.dbg('IPA received %s::%s [%d/%d] %s' % (IPA().proto(proto), IPA().ext_name(proto, extension), len(data), len(content), content)) method = getattr(self, 'handle_' + IPA().proto(proto), lambda: "protocol dispatch failure") method(content, proto, extension)
def recv_msgs(self): responses = {} data = self.sock.recv(4096) while (len(data) > 0): (head, data) = IPA().split_combined(data) answer = Ctrl().rem_header(head) if verbose: print "Got message:", answer (mtype, id, msg) = answer.split(None, 2) id = int(id) rsp = {'mtype': mtype, 'id': id} if mtype == "ERROR": rsp['error'] = msg else: split = msg.split(None, 1) rsp['var'] = split[0] if len(split) > 1: rsp['value'] = split[1] else: rsp['value'] = None responses[id] = rsp if verbose: print "Decoded replies: ", responses return responses
class IPAFactory(ReconnectingClientFactory): """ Generic IPA Client Factory which can be used to store state for various subprotocols and manage connections Note: so far we do not really need separate Factory for acting as a server due to protocol simplicity """ protocol = IPACommon log = None ccm_id = IPA().identity(unit=b'1515/0/1', mac=b'b0:0b:fa:ce:de:ad:be:ef', utype=b'sysmoBTS', name=b'StingRay', location=b'hell', sw=IPA.version.encode('utf-8')) def __init__(self, proto=None, log=None, ccm_id=None): if proto: self.protocol = proto if ccm_id: self.ccm_id = ccm_id if log: self.log = log else: self.log = logging.getLogger('IPAFactory') self.log.setLevel(logging.CRITICAL) self.log.addHandler(logging.NullHandler) def clientConnectionFailed(self, connector, reason): """ Only necessary for as debugging aid - if we can somehow set parent's class noisy attribute then we can omit this method """ self.log.warning('IPAFactory connection failed: %s' % reason.getErrorMessage()) ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) def clientConnectionLost(self, connector, reason): """ Only necessary for as debugging aid - if we can somehow set parent's class noisy attribute then we can omit this method """ self.log.warning('IPAFactory connection lost: %s' % reason.getErrorMessage()) ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
def receive(self): responses = [] data = self.socket.recv(4096) while (len(data)>0): (response_with_header, data) = IPA().split_combined(data) response = Ctrl().rem_header(response_with_header) responses.append(response.decode('utf-8')) return responses
def connectionMade(self): """ Keep reconnection logic working by calling routine from CCM Initiate CCM upon connection """ addr = self.transport.getPeer() self.factory.log.info('IPA server: connection from %s:%d client' % (addr.host, addr.port)) super(IPAServer, self).connectionMade() self.transport.write(IPA().id_get())
def dataReceived(self, data): """ Override for dataReceived from Int16StringReceiver because of inherently incompatible interpretation of length If default handler is used than we would always get off-by-1 error (Int16StringReceiver use equivalent of l + 2) """ if len(data): (head, tail) = IPA().split_combined(data) self.process_chunk(head) self.dataReceived(tail)
def ipa_handle_small(x, verbose=False): s = data2str(x.recv(4)) if len(s) != 4 * 2: raise Exception("expected to receive 4 bytes, but got %d (%r)" % (len(s) / 2, s)) if "0001fe00" == s: if (verbose): print "\tBSC <- NAT: PING?" x.send(IPA().pong()) elif "0001fe06" == s: if (verbose): print "\tBSC <- NAT: IPA ID ACK" x.send(IPA().id_ack()) elif "0001fe00" == s: if (verbose): print "\tBSC <- NAT: PONG!" else: if (verbose): print "\tBSC <- NAT: ", s
def ipa_handle_resp(x, tk, verbose=False, proc=None): s = data2str(x.recv(38)) if "0023fe040108010701020103010401050101010011" in s: retries = 3 while True: print "\tsending IPA identity(%s) at %s" % (tk, time.strftime("%T")) try: x.send(IPA().id_resp(IPA().identity(name=tk.encode('utf-8')))) print "\tdone sending IPA identity(%s) at %s" % ( tk, time.strftime("%T")) break except: print "\tfailed sending IPA identity at", time.strftime("%T") if proc: print "\tproc.poll() = %r" % proc.poll() if retries < 1: print "\tgiving up" raise print "\tretrying (%d attempts left)" % retries retries -= 1 else: if (verbose): print "\tBSC <- NAT: ", s
def handle_CCM(self, data, proto, msgt): """ CCM (IPA Connection Management) Only basic logic necessary for tests is implemented (ping-pong, id ack etc) """ if msgt == IPA.MSGT['ID_GET']: self.transport.getHandle().sendall(IPA().id_resp(self.factory.ccm_id)) # if we call # self.transport.write(IPA().id_resp(self.factory.test_id)) # instead, than we would have to also call # reactor.callLater(1, self.ack) # instead of self.ack() # otherwise the writes will be glued together - hence the necessity for ugly hack with 1s timeout # Note: this still might work depending on the IPA implementation details on the other side self.ack() # schedule PING in 4s reactor.callLater(4, self.ping) if msgt == IPA.MSGT['PING']: self.pong()
def pong(self): self.transport.write(IPA().pong())
def ack(self): self.transport.write(IPA().id_ack())
def handle_UNKNOWN(self, data, proto, extension): """ Default protocol handler """ self.dbg('IPA received message for %s (%s) protocol with attribute %s' % (IPA().proto(proto), proto, extension))
def handle_OSMO(self, data, proto, extension): """ Dispatcher point for OSMO subprotocols based on extension name, lambda default should never happen """ method = getattr(self, 'osmo_' + IPA().ext(extension), lambda: "extension dispatch failure") method(data)