def gen_nak(self, req, nak=gdp_pb2.NAK_C_BADREQ): resp = dict() resp['src'] = req['dst'] resp['dst'] = req['src'] resp_payload = gdp_pb2.GdpMessage() resp_payload.ParseFromString(req['data']) resp_payload.cmd = nak resp_payload.nak.ep_stat = 0 resp['data'] = resp_payload.SerializeToString() return resp
def gen_nak(self, req, nak=gdp_pb2.NAK_C_BADREQ): logging.info("sending a NAK(%d) [src:%r, dst:%r]" % (nak, self.printable_name( req['dst']), self.printable_name(req['src']))) resp = dict() resp['src'] = req['dst'] resp['dst'] = req['src'] resp_payload = gdp_pb2.GdpMessage() resp_payload.ParseFromString(req['data']) resp_payload.cmd = nak resp_payload.nak.ep_stat = 0 resp['data'] = resp_payload.SerializeToString() return resp
def request_handler(self, req): """ The routine that gets invoked when a PDU is received. In our case, it can be either a new request (from a client), or a response from a log-server. """ # first, deserialize the payload from req pdu. payload = gdp_pb2.GdpMessage() payload.ParseFromString(req['data']) # early exit if a router told us something (usually not a good # sign) if payload.cmd >= GDP_NAK_R_MIN and \ payload.cmd <= GDP_NAK_R_MAX: logger.warning("Routing error, src: %r", req['src']) return # check if it's a request from a client or a response from a logd. if payload.cmd < GDP_ACK_MIN: ## it's a command ## First check for any error conditions. If any of the ## following occur, we ought to send back a NAK if req['src'] in self.logservers: logging.warning("error: received cmd %d from server", payload.cmd) return self.gen_nak(req, gdp_pb2.NAK_C_BADREQ) if payload.cmd != gdp_pb2.CMD_CREATE: logging.warning("error: recieved unknown request") return self.gen_nak(req, gdp_pb2.NAK_S_NOTIMPL) ## By now, we know the request is a CREATE request from a client ## figure out the data we need to insert in the database humanname, logname = self.extract_name(payload.cmd_create) if humanname is not None and self.namedb_conn is not None: ## Add this to the humanname=>logname mapping directory try: # Note that logname is the internal 256-bit name # (and not a printable name) self.add_to_hongd(humanname, logname) except mariadb.Error as error: logging.warning("Couldn't add mapping. %s", str(error)) ## XXX what is the correct behavior? exit? return self.gen_nak(req, gdp_pb2.NAK_C_CONFLICT) srvname = random.choice(self.logservers) ## private information that we will need later creator = req['src'] rid = payload.rid ## log this to our backend database. Generate printable ## names (except the null character, which causes problems) __logname = gdp.GDP_NAME(logname, force_internal=True).printable_name()[:-1] __srvname = gdp.GDP_NAME(srvname, force_internal=True).printable_name()[:-1] __creator = gdp.GDP_NAME(creator, force_internal=True).printable_name()[:-1] logging.info( "Received Create request for logname %r, " "picking server %r", __logname, __srvname) try: with self.lock: logging.debug("inserting to database %r, %r, %r, %d", __logname, __srvname, __creator, rid) self.cur.execute( """INSERT INTO logs (logname, srvname, creator, rid) VALUES(?,?,?,?);""", (__logname, __srvname, __creator, rid)) self.conn.commit() spoofed_req = req.copy() spoofed_req['src'] = req['dst'] spoofed_req['dst'] = srvname ## make a copy of payload so that we can change rid __spoofed_payload = gdp_pb2.GdpMessage() __spoofed_payload.ParseFromString(req['data']) __spoofed_payload.rid = self.cur.lastrowid spoofed_req['data'] = __spoofed_payload.SerializeToString() # now return this spoofed request back to transport layer # Since we have overridden the destination, it will go # to a log server instead of the actual client return spoofed_req except sqlite3.IntegrityError: logging.warning("Log already exists") return self.gen_nak(req, gdp_pb2.NAK_C_CONFLICT) ## Send a spoofed request to the logserver else: ## response. ## Sanity checking if req['src'] not in self.logservers: logging.warning( "error: received a response from non-logserver") return self.gen_nak(req, gdp_pb2.NAK_C_BADREQ) logging.info("Response from log-server, row %d", payload.rid) with self.lock: ## Fetch the original creator and rid from our database self.cur.execute( """SELECT creator, rid, ack_seen FROM logs WHERE rowid=?""", (payload.rid, )) dbrows = self.cur.fetchall() good_resp = len(dbrows) == 1 if good_resp: (__creator, orig_rid, ack_seen) = dbrows[0] creator = gdp.GDP_NAME(__creator).internal_name() if ack_seen != 0: good_resp = False if not good_resp: logging.warning("error: bogus response") return self.gen_nak(req, gdp_pb2.NAK_C_BADREQ) else: with self.lock: logging.debug("Setting ack_seen to 1 for row %d", payload.rid) self.cur.execute( """UPDATE logs SET ack_seen=1 WHERE rowid=?""", (payload.rid, )) self.conn.commit() # create a spoofed reply and send it to the client spoofed_reply = req.copy() spoofed_reply['src'] = req['dst'] spoofed_reply['dst'] = creator spoofed_payload = gdp_pb2.GdpMessage() spoofed_payload.ParseFromString(req['data']) spoofed_payload.rid = orig_rid spoofed_reply['data'] = spoofed_payload.SerializeToString() # return this back to the transport layer return spoofed_reply
def request_handler(self, req): """ The routine that gets invoked when a PDU is received. In our case, it can be either a new request (from a client), or a response from a log-server. """ # first, deserialize the payload from req pdu. payload = gdp_pb2.GdpMessage() payload.ParseFromString(req['data']) # early exit if a router told us something (usually not a good # sign) if payload.cmd >= GDP_NAK_R_MIN and \ payload.cmd <= GDP_NAK_R_MAX: logger.warning("Routing error, src: %r", self.printable_name(req['src'])) return # check if it's a request from a client or a response from a logd. if payload.cmd < GDP_ACK_MIN: ## it's a command ## First check for any error conditions. If any of the ## following occur, we ought to send back a NAK if req['src'] in self.logservers: logging.warning("error: received cmd %d from server", payload.cmd) return self.gen_nak(req, gdp_pb2.NAK_C_BADREQ) if payload.cmd != gdp_pb2.CMD_CREATE: logging.warning("error: recieved unknown request") return self.gen_nak(req, gdp_pb2.NAK_S_NOTIMPL) ## By now, we know the request is a CREATE request from a client ## figure out the data we need to insert in the database humanname, logname = self.extract_name(payload.cmd_create) ## Add this to the humanname=>logname mapping directory #FIXME this shouldn't be done before the log is successfully #FIXME created on the server! if humanname is not None and self.namedb_cpool is not None: try: # Note that logname is the internal 256-bit name # (and not a printable name) self.add_to_hongd(humanname, logname) except mariadb.Error as error: # XXX we should probably handle the situation where # this is simply a retransmit from an aggressive client. logging.warning("Couldn't add mapping. %s", str(error)) return self.gen_nak(req, gdp_pb2.NAK_C_CONFLICT) except PoolExhausted as error: logging.warning("DB connection pool exhausted") return self.gen_nak(req, gdp_pb2.NAK_S_INTERNAL) except Exception as e: ## report back to the client that something went wrong logging.warning("Generic exception: %r", e) return self.gen_nak(req, gdp_pb2.NAK_S_INTERNAL) srvname = random.choice(self.logservers) ## private information that we will need later creator = req['src'] rid = payload.rid ## log this to our backend database. Generate printable ## names (except the null character, which causes problems) __logname = self.printable_name(logname) __srvname = self.printable_name(srvname) __creator = self.printable_name(creator) logging.info( "Received Create request for logname %r, " "picking server %r", __logname, __srvname) try: ## Add this information to the dupdb, and get back the ## row ID in the database that we will include in the spoofed ## request to an actual log-server __rid = self.add_to_dupdb(__logname, __srvname, __creator, rid) spoofed_req = req.copy() spoofed_req['src'] = req['dst'] spoofed_req['dst'] = srvname ## make a copy of payload so that we can change rid __spoofed_payload = gdp_pb2.GdpMessage() __spoofed_payload.ParseFromString(req['data']) __spoofed_payload.rid = __rid spoofed_req['data'] = __spoofed_payload.SerializeToString() # now return this spoofed request back to transport layer # Since we have overridden the destination, it will go # to a log server instead of the actual client return spoofed_req except sqlite3.IntegrityError: ## We reach here if we are trying to add a duplicate ## entry in our database (which is the whole purpose ## of such bookkeeping). ## We send a NAK to the creator. logging.warning("Log already exists") return self.gen_nak(req, gdp_pb2.NAK_C_CONFLICT) except Exception as e: ## Generic exception logging.warning("Got generic exception %r", e) return self.gen_nak(req, gdp_pb2.NAK_S_INTERNAL) else: ## response. ## Sanity checking if req['src'] not in self.logservers: logging.warning( "error: received a response from non-logserver") return self.gen_nak(req, gdp_pb2.NAK_C_BADREQ) logging.info("Response from log-server, row %d", payload.rid) try: (creator, orig_rid) = self.fetch_creator(payload.rid) except UnknownResponse as e: ## this happens because either we don't know about the ## specific request ID, or we have already received a ## response for the said request ID. In any case, it is ## appropriate to return a NAK back to the log-server logging.warning("error: bogus response") return self.gen_nak(req, gdp_pb2.NAK_C_BADREQ) except Exception as e: ## We shouldn't be returning any NAK back to a log-server. ## Ideally, we should be returning a NAK back to the original ## client, but we don't have a name for the client yet. The ## most appropriate strategy is to simply report the error on ## the console. logging.warning("Generic exception: %r", e) return None # create a spoofed reply and send it to the client spoofed_reply = req.copy() spoofed_reply['src'] = req['dst'] spoofed_reply['dst'] = creator spoofed_payload = gdp_pb2.GdpMessage() spoofed_payload.ParseFromString(req['data']) spoofed_payload.rid = orig_rid spoofed_reply['data'] = spoofed_payload.SerializeToString() # return this back to the transport layer return spoofed_reply