Implemented per-interface protocol violation tracking
parent
6ba7cc8cb4
commit
42f6d64b9c
|
|
@ -146,6 +146,10 @@ class Interface:
|
|||
self.ip_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
|
||||
self.op_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
|
||||
|
||||
self.protocol_violations = 0
|
||||
self.ifac_violations = 0
|
||||
self.packet_filter_hits = 0
|
||||
|
||||
def get_hash(self):
|
||||
if not self.__hash: self.__hash = RNS.Identity.full_hash(str(self).encode("utf-8"))
|
||||
return self.__hash
|
||||
|
|
@ -286,6 +290,20 @@ class Interface:
|
|||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.sent_path_request(size=size, from_spawned=True)
|
||||
|
||||
def protocol_violation(self, description=None):
|
||||
self.protocol_violations += 1
|
||||
RNS.log(f"Protocol violation on {self}: {description}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
return None
|
||||
|
||||
def ifac_violation(self, description=None):
|
||||
self.ifac_violations += 1
|
||||
RNS.log(f"IFAC violation on {self}: {description}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
return None
|
||||
|
||||
def packet_filter_hit(self):
|
||||
self.packet_filter_hits += 1
|
||||
return None
|
||||
|
||||
@property
|
||||
def ic_burst_count(self): return None
|
||||
|
||||
|
|
|
|||
|
|
@ -261,11 +261,14 @@ class Packet:
|
|||
self.destination_hash = self.raw[DST_LEN+2:2*DST_LEN+2]
|
||||
self.context = ord(self.raw[2*DST_LEN+2:2*DST_LEN+3])
|
||||
self.data = self.raw[2*DST_LEN+3:]
|
||||
if len(self.transport_id) != DST_LEN: raise ValueError("Malformed Transport ID field")
|
||||
if len(self.destination_hash) != DST_LEN: raise ValueError("Malformed destination hash field")
|
||||
else:
|
||||
self.transport_id = None
|
||||
self.destination_hash = self.raw[2:DST_LEN+2]
|
||||
self.context = ord(self.raw[DST_LEN+2:DST_LEN+3])
|
||||
self.data = self.raw[DST_LEN+3:]
|
||||
if len(self.destination_hash) != DST_LEN: raise ValueError("Malformed destination hash field")
|
||||
|
||||
self.packed = False
|
||||
self.update_hash()
|
||||
|
|
|
|||
|
|
@ -1546,6 +1546,9 @@ class Reticulum:
|
|||
ifstats["mode"] = interface.mode
|
||||
ifstats["gravity"] = interface.gravity
|
||||
ifstats["announces_to_internal"] = interface.announces_to_internal
|
||||
ifstats["protocol_violations"] = interface.protocol_violations
|
||||
ifstats["ifac_violations"] = interface.ifac_violations
|
||||
ifstats["packet_filter_hits"] = interface.packet_filter_hits
|
||||
|
||||
interfaces.append(ifstats)
|
||||
|
||||
|
|
|
|||
|
|
@ -1264,6 +1264,7 @@ class Transport:
|
|||
def _outbound(packet):
|
||||
sent = False
|
||||
outbound_time = time.time()
|
||||
if packet.hops > Transport.PATHFINDER_M-1: return False
|
||||
|
||||
generate_receipt = False
|
||||
if (packet.create_receipt == True and
|
||||
|
|
@ -1634,27 +1635,28 @@ class Transport:
|
|||
|
||||
# Check it
|
||||
if ifac == expected_ifac: raw = new_raw
|
||||
else: return
|
||||
else: return interface.ifac_violation("Invalid IFAC on packet")
|
||||
|
||||
else: return
|
||||
else: return interface.ifac_violation("Insufficient packet size for IFAC packet")
|
||||
|
||||
# If the IFAC flag is not set, but should be,
|
||||
# drop the packet.
|
||||
else: return
|
||||
else: return interface.ifac_violation("Missing IFAC flag on packet for IFAC-enabled interface")
|
||||
|
||||
else:
|
||||
# If the interface does not have IFAC enabled,
|
||||
# check the received packet IFAC flag.
|
||||
# If the flag is set, drop the packet
|
||||
if raw[0] & 0x80 == 0x80: return
|
||||
if raw[0] & 0x80 == 0x80:
|
||||
return interface.protocol_violation("IFAC flag set on packet for interface without IFAC enabled") if interface else None
|
||||
|
||||
else: return
|
||||
else: return interface.protocol_violation("Insufficient packet size for IFAC processing") if interface else None
|
||||
|
||||
if Transport.identity == None: return
|
||||
|
||||
packet = RNS.Packet(None, raw)
|
||||
if not packet.unpack(): return
|
||||
if not Transport.packet_filter(packet): return
|
||||
if not packet.unpack(): return interface.protocol_violation("Malformed packet") if interface else None
|
||||
if not Transport.packet_filter(packet): return interface.packet_filter_hit()
|
||||
traffic_class = tc or Transport.TC_DATA
|
||||
|
||||
packet.receiving_interface = interface
|
||||
|
|
@ -1664,7 +1666,10 @@ class Transport:
|
|||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
if not tc: traffic_class = Transport.TC_ANNOUNCE
|
||||
announce_signature_valid = RNS.Identity.validate_announce(packet, only_validate_signature=True)
|
||||
if not announce_signature_valid: return
|
||||
if not announce_signature_valid:
|
||||
if not packet.receiving_interface: return None
|
||||
else: return packet.receiving_interface.protocol_violation(f"Invalid announce signature for {RNS.prettyhexrep(packet.destination_hash)}") if not packet.destination_hash in Transport.blackholed_identities else None
|
||||
|
||||
elif packet.receiving_interface != None: packet.receiving_interface.received_announce(size=len(packet.raw))
|
||||
announced_destination_known = packet.destination_hash in Transport.path_table
|
||||
|
||||
|
|
@ -1694,7 +1699,7 @@ class Transport:
|
|||
|
||||
if tag_bytes == None:
|
||||
RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||
return
|
||||
return packet.receiving_interface.protocol_violation("Tagless path request") if packet.receiving_interface else None
|
||||
|
||||
else:
|
||||
if len(tag_bytes) > RNS.Identity.TRUNCATED_HASHLENGTH//8: tag_bytes = tag_bytes[:RNS.Identity.TRUNCATED_HASHLENGTH//8]
|
||||
|
|
@ -1754,10 +1759,17 @@ class Transport:
|
|||
@staticmethod
|
||||
def inbound_job():
|
||||
while Transport._should_run:
|
||||
try: Transport._inbound(Transport.inbound_queues.get())
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while draining inbound queue: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
try: packet = Transport.inbound_queues.get()
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while draining inbound queue: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
continue
|
||||
|
||||
try: Transport._inbound(packet)
|
||||
except Exception as e:
|
||||
packet.receiving_interface.protocol_violation("Processing caused exception") if packet.receiving_interface else None
|
||||
RNS.log(f"Error while processing inbound packet: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
@staticmethod
|
||||
def _inbound(packet):
|
||||
|
|
@ -1867,7 +1879,8 @@ class Transport:
|
|||
# are the designated next hop, and process it
|
||||
# accordingly if we are.
|
||||
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
|
||||
if packet.transport_id == Transport.identity.hash:
|
||||
if not packet.transport_id == Transport.identity.hash: return
|
||||
else:
|
||||
if packet.destination_hash in Transport.path_table:
|
||||
next_hop = Transport.path_table[packet.destination_hash][IDX_PT_NEXT_HOP]
|
||||
remaining_hops = Transport.path_table[packet.destination_hash][IDX_PT_HOPS]
|
||||
|
|
@ -1971,6 +1984,10 @@ class Transport:
|
|||
if packet.packet_type != RNS.Packet.ANNOUNCE and packet.packet_type != RNS.Packet.LINKREQUEST and packet.context != RNS.Packet.LRPROOF:
|
||||
if packet.destination_hash in Transport.link_table:
|
||||
link_entry = Transport.link_table[packet.destination_hash]
|
||||
if not link_entry[IDX_LT_VALIDATED]:
|
||||
RNS.log(f"Pre-validation link packet from {packet.receiving_interface}", RNS.LOG_WARNING) # TODO: Remove
|
||||
return packet.receiving_interface.protocol_violation("Link packet received before link validation") if packet.receiving_interface else None
|
||||
|
||||
# If receiving and outbound interface is
|
||||
# the same for this link, direction doesn't
|
||||
# matter, and we simply repeat the packet.
|
||||
|
|
@ -2004,9 +2021,12 @@ class Transport:
|
|||
new_raw += packet.raw[2:]
|
||||
Transport.transmit(outbound_interface, new_raw)
|
||||
Transport.link_table[packet.destination_hash][IDX_LT_TIMESTAMP] = time.time()
|
||||
|
||||
else:
|
||||
RNS.log(f"No-outbound return on link packet from {packet.receiving_interface}", RNS.LOG_WARNING) # TODO: Remove
|
||||
|
||||
# TODO: Can we return safely here? Test and possibly enable this at some point.
|
||||
# return
|
||||
return
|
||||
|
||||
|
||||
# Announce handling. Handles logic related to incoming
|
||||
|
|
@ -2018,7 +2038,10 @@ class Transport:
|
|||
if packet.destination_hash in Transport.destinations_map:
|
||||
local_destination = Transport.destinations_map[packet.destination_hash]
|
||||
|
||||
if local_destination == None and RNS.Identity.validate_announce(packet):
|
||||
announce_valid = RNS.Identity.validate_announce(packet)
|
||||
if not announce_valid: return packet.receiving_interface.protocol_violation("Invalid announce") if packet.receiving_interface else None
|
||||
|
||||
if local_destination == None and announce_valid:
|
||||
if packet.transport_id != None:
|
||||
received_from = packet.transport_id
|
||||
|
||||
|
|
@ -2058,8 +2081,10 @@ class Transport:
|
|||
if local_and_hops_condition:
|
||||
announce_emitted = Transport.announce_emitted(packet)
|
||||
|
||||
random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
|
||||
random_blobs = []
|
||||
random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
|
||||
if not random_blob: return packet.receiving_interface.protocol_violation("No random blob in data") if packet.receiving_interface else None
|
||||
|
||||
with Transport.inbound_announce_lock:
|
||||
announced_destination_known = packet.destination_hash in Transport.path_table
|
||||
if not announced_destination_known:
|
||||
|
|
@ -2512,7 +2537,10 @@ class Transport:
|
|||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
RNS.Identity._used_destination_data(link_entry[IDX_LT_DSTHASH])
|
||||
|
||||
else: RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else:
|
||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
return packet.receiving_interface.protocol_violation("Invalid link request proof in transport") if packet.receiving_interface else None
|
||||
|
||||
except Exception as e: RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else: RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else: RNS.log(f"Received link request proof with hop mismatch ({packet.hops}/{link_entry[IDX_LT_REM_HOPS]}:{link_entry[IDX_LT_NH_IF]}->{link_entry[IDX_LT_RCVD_IF]}), not transporting it", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None
|
||||
|
|
|
|||
|
|
@ -391,10 +391,16 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
interfaces.sort(key=lambda i: i["outgoing_pr_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "held":
|
||||
interfaces.sort(key=lambda i: i["held_announces"], reverse=not sort_reverse)
|
||||
if sorting == "pvs":
|
||||
interfaces.sort(key=lambda i: i["protocol_violations"], reverse=not sort_reverse)
|
||||
if sorting == "ivs":
|
||||
interfaces.sort(key=lambda i: i["ifac_violations"], reverse=not sort_reverse)
|
||||
if sorting == "flt":
|
||||
interfaces.sort(key=lambda i: i["packet_filter_hits"], reverse=not sort_reverse)
|
||||
if sorting == "gravity" or sorting == "g":
|
||||
interfaces.sort(key=lambda i: i["gravity"], reverse=not sort_reverse)
|
||||
|
||||
|
||||
|
||||
for ifstat in interfaces:
|
||||
name = ifstat["name"]
|
||||
|
||||
|
|
@ -656,6 +662,16 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
rxb_str += (mlen-len(rxb_str))*" "
|
||||
txb_str += (mlen-len(txb_str))*" "
|
||||
|
||||
if "protocol_violations" in ifstat and "ifac_violations" in ifstat:
|
||||
pv = ifstat["protocol_violations"]; iv = ifstat["ifac_violations"]
|
||||
if pv or iv:
|
||||
pvstr = f"{pv} protocol{', ' if iv else ''}"
|
||||
ivstr = f"{iv} IFAC" if iv else ""
|
||||
print(f" Violatns. : {pvstr}{ivstr}")
|
||||
|
||||
if "packet_filter_hits" in ifstat and ifstat["packet_filter_hits"]:
|
||||
print(f" Flt. Hits : {ifstat['packet_filter_hits']}")
|
||||
|
||||
if psr:
|
||||
print(f" Path Rqs. : {opf} {rpc_str}")
|
||||
print(f" {ipf} {pburst_str}")
|
||||
|
|
@ -719,7 +735,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
|
||||
prxstat = prxb_str+" "+RNS.prettyspeed(stats["prxs"])+f", {int(prxspct)}% of flow"
|
||||
ptxstat = ptxb_str+" "+RNS.prettyspeed(stats["ptxs"])+f", {int(ptxspct)}% of flow"
|
||||
print(f"\n Path Reqs. : {ptxstat}\n {prxstat}")
|
||||
print(f"\n Path Rqs. : {ptxstat}\n {prxstat}")
|
||||
|
||||
if astats:
|
||||
if "arxb" in stats and "atxb" in stats and "arxs" in stats and "atxs" in stats:
|
||||
|
|
@ -793,7 +809,7 @@ def main(must_exit=True, rns_instance=None):
|
|||
parser.add_argument("-b", "--blocked-ips", action="store_true", help="show blocked IPs per interface", default=False)
|
||||
parser.add_argument("-t", "--totals", action="store_true", help="display traffic totals", default=False)
|
||||
parser.add_argument("-q", "--queues", action="store_true", help="display queue stats", default=False)
|
||||
parser.add_argument("-s", "--sort", action="store", help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, prx, ptx, held]", default=None, type=str)
|
||||
parser.add_argument("-s", "--sort", action="store", help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, held, prx, ptx, pvs, ivs, flt]", default=None, type=str)
|
||||
parser.add_argument("-r", "--reverse", action="store_true", help="reverse sorting", default=False)
|
||||
parser.add_argument("-j", "--json", action="store_true", help="output in JSON format", default=False)
|
||||
parser.add_argument("-R", action="store", metavar="hash", help="transport identity hash of remote instance to get status from", default=None, type=str)
|
||||
|
|
|
|||
Loading…
Reference in New Issue