Implemented loop detection for pathfinding and congestionn avoidance for announce rebroadcasts.

This commit is contained in:
Mark Qvist 2019-11-10 13:56:04 +01:00
parent 64c30488c2
commit 8e19d5bd97
2 changed files with 61 additions and 15 deletions

View File

@ -19,12 +19,19 @@ class Transport:
REACHABILITY_DIRECT = 0x01 REACHABILITY_DIRECT = 0x01
REACHABILITY_TRANSPORT = 0x02 REACHABILITY_TRANSPORT = 0x02
# TODO: Document the addition of random windows
# and max local rebroadcasts.
PATHFINDER_M = 18 # Max hops PATHFINDER_M = 18 # Max hops
PATHFINDER_C = 2.0 # Decay constant PATHFINDER_C = 2.0 # Decay constant
PATHFINDER_R = 2 # Retransmit retries PATHFINDER_R = 2 # Retransmit retries
PATHFINDER_T = 10 # Retry grace period PATHFINDER_T = 10 # Retry grace period
PATHFINDER_RW = 10 # Random window for announce rebroadcast
PATHFINDER_E = 60*15 # Path expiration in seconds PATHFINDER_E = 60*15 # Path expiration in seconds
# TODO: Calculate an optimal number for this in
# various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
interfaces = [] # All active interfaces interfaces = [] # All active interfaces
destinations = [] # All active destinations destinations = [] # All active destinations
pending_links = [] # Links that are being established pending_links = [] # Links that are being established
@ -32,8 +39,8 @@ class Transport:
packet_hashlist = [] # A list of packet hashes for duplicate detection packet_hashlist = [] # A list of packet hashes for duplicate detection
receipts = [] # Receipts of all outgoing packets for proof processing receipts = [] # Receipts of all outgoing packets for proof processing
announce_table = {} announce_table = {} # A table for storing announces currently waiting to be retransmitted
destination_table = {} destination_table = {} # A lookup table containing the next hop to a given destination
jobs_locked = False jobs_locked = False
jobs_running = False jobs_running = False
@ -113,8 +120,7 @@ class Transport:
break break
else: else:
if time.time() > announce_entry[1]: if time.time() > announce_entry[1]:
# RNS.log("Rebroadcasting announce", RNS.LOG_INFO) announce_entry[1] = time.time() + math.pow(Transport.PATHFINDER_C, announce_entry[4]) + Transport.PATHFINDER_T + Transport.PATHFINDER_RW
announce_entry[1] = time.time() + math.pow(Transport.PATHFINDER_C, announce_entry[4]) + Transport.PATHFINDER_T
announce_entry[2] += 1 announce_entry[2] += 1
packet = announce_entry[5] packet = announce_entry[5]
announce_data = packet.data announce_data = packet.data
@ -221,12 +227,21 @@ class Transport:
# Check if this is a next retransmission from # Check if this is a next retransmission from
# another node. If it is, we're removing the # another node. If it is, we're removing the
# announcein question from our pending table # announce in question from our pending table
if packet.destination_hash in Transport.announce_table: if packet.destination_hash in Transport.announce_table:
announce_entry = Transport.announce_table[packet.destination_hash] announce_entry = Transport.announce_table[packet.destination_hash]
if packet.hops == announce_entry[4]:
RNS.log("Heard a local rebroadcast of announce for "+RNS.prettyhexrep(packet.destination_hash), RNS.LOG_DEBUG)
announce_entry[6] += 1
if announce_entry[6] >= Transport.LOCAL_REBROADCASTS_MAX:
RNS.log("Max local rebroadcasts of announce for "+RNS.prettyhexrep(packet.destination_hash)+" reached, dropping announce from our table", RNS.LOG_DEBUG)
Transport.announce_table.pop(packet.destination_hash)
if packet.hops == announce_entry[4]+1 and announce_entry[2] > 0: if packet.hops == announce_entry[4]+1 and announce_entry[2] > 0:
now = time.time() now = time.time()
if now < announce_entry[1]: if now < announce_entry[1]:
RNS.log("Rebroadcasted announce for "+RNS.prettyhexrep(packet.destination_hash)+" has been passed on to next node, no further tries needed", RNS.LOG_DEBUG)
Transport.announce_table.pop(packet.destination_hash) Transport.announce_table.pop(packet.destination_hash)
else: else:
@ -236,22 +251,44 @@ class Transport:
# announce and destination tables # announce and destination tables
should_add = False should_add = False
packet.hops += 1 packet.hops += 1
# First, check that the announce is not for a destination
# local to this system, and that hops are less than the max
if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1): if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
random_blob = packet.data[RNS.Identity.DERKEYSIZE/8+10:RNS.Identity.DERKEYSIZE/8+20]
random_blobs = []
if packet.destination_hash in Transport.destination_table: if packet.destination_hash in Transport.destination_table:
if packet.hops <= Transport.destination_table[packet.destination_hash][2]: random_blobs = Transport.destination_table[packet.destination_hash][4]
# If we already have a path to the announced # If we already have a path to the announced
# destination, but the hop count is equal or # destination, but the hop count is equal or
# less, we'll update our tables. # less, we'll update our tables.
if packet.hops <= Transport.destination_table[packet.destination_hash][2]:
# Make sure we haven't heard the random
# blob before, so announces can't be
# replayed to forge paths.
# TODO: Check whether this approach works
# under all circumstances
if not random_blob in random_blobs:
should_add = True should_add = True
else:
should_add = False
else: else:
# If an announce arrives with a larger hop # If an announce arrives with a larger hop
# count than we already have in the table, # count than we already have in the table,
# ignore it, unless the path is expired # ignore it, unless the path is expired
if (time.time() < Transport.destination_table[packet.destination_hash][3]): if (time.time() > Transport.destination_table[packet.destination_hash][3]):
# We also check that the announce hash is
# different from ones we've already heard,
# to avoid loops in the network
if not random_blob in random_blobs:
# TODO: Check that this ^ approach actually
# works under all circumstances
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce due to expired path", RNS.LOG_DEBUG) RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce due to expired path", RNS.LOG_DEBUG)
should_add = True should_add = True
else: else:
should_add = False should_add = False
else:
should_add = False
else: else:
# If this destination is unknown in our table # If this destination is unknown in our table
# we should add it # we should add it
@ -261,9 +298,11 @@ class Transport:
now = time.time() now = time.time()
retries = 0 retries = 0
expires = now + Transport.PATHFINDER_E expires = now + Transport.PATHFINDER_E
retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) local_rebroadcasts = 0
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, packet.hops, packet] random_blobs.append(random_blob)
Transport.destination_table[packet.destination_hash] = [now, received_from, packet.hops, expires] retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) + (RNS.rand() * Transport.PATHFINDER_RW)
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, packet.hops, packet, local_rebroadcasts]
Transport.destination_table[packet.destination_hash] = [now, received_from, packet.hops, expires, random_blobs]
elif packet.packet_type == RNS.Packet.LINKREQUEST: elif packet.packet_type == RNS.Packet.LINKREQUEST:
for destination in Transport.destinations: for destination in Transport.destinations:

View File

@ -2,6 +2,7 @@ import os
import sys import sys
import glob import glob
import time import time
import random
from .Reticulum import Reticulum from .Reticulum import Reticulum
from .Identity import Identity from .Identity import Identity
@ -32,6 +33,8 @@ logfile = None
logdest = LOG_STDOUT logdest = LOG_STDOUT
logtimefmt = "%Y-%m-%d %H:%M:%S" logtimefmt = "%Y-%m-%d %H:%M:%S"
random.seed(os.urandom(10))
def loglevelname(level): def loglevelname(level):
if (level == LOG_CRITICAL): if (level == LOG_CRITICAL):
return "Critical" return "Critical"
@ -66,6 +69,10 @@ def log(msg, level=3):
file.write(logstring+"\n") file.write(logstring+"\n")
file.close() file.close()
def rand():
result = random.random()
return result
def hexprint(data): def hexprint(data):
print(hexrep(hexrep)) print(hexrep(hexrep))