Implemented the first three phases of neighbour management (warning: for testing purposes, the delays before any action have been reduced)

This commit is contained in:
eichhornchen 2020-12-27 21:31:34 +01:00
parent 806287f834
commit b7a495eb11
3 changed files with 150 additions and 28 deletions

View File

@ -8,8 +8,9 @@ from threading import Thread, RLock
import curses
import re
import socket
import time
from .messages import Packet, DataTLV
from .messages import Packet, DataTLV, HelloTLV, GoAwayTLV, GoAwayType
class Hazelnut:
@ -72,8 +73,13 @@ class Squirrel(Hazelnut):
#dictionnaries of neighbours
self.potentialhazelnuts = dict()
self.activehazelnuts = dict() #of the form [hazelnut, time of last
#hello, time of last long hello, is symetric]
#hello, time of last long hello]
self.nbNS = 0
self.minNS = 3 #minimal number of symetric neighbours a squirrel needs
#to have.
self.add_system_message(f"Listening on {self.address}:{self.port}")
self.add_system_message(f"I am {self.id}")
def new_hazel(self, address: IPv6Address, port: int) -> Hazelnut:
"""
@ -89,7 +95,7 @@ class Squirrel(Hazelnut):
return (hazel.address, hazel.port) in self.potentialhazelnuts
def remove_from_potential(self, address: IPv6Address, port: int)-> None:
self.potentialhazelnuts.pop((address, port), None)
self.potentialhazelnuts.pop((str(address), port), None)
def find_hazelnut(self, address: str, port: int) -> Hazelnut:
"""
@ -105,16 +111,22 @@ class Squirrel(Hazelnut):
"""
Send a formatted packet to a client.
"""
self.refresh_lock.acquire()
if len(pkt) > 1024:
# The packet is too large to be sent by the protocol. We split the packet in subpackets.
return sum(self.send_packet(client, subpkt) for subpkt in pkt.split(1024))
return self.send_raw_data(client, pkt.marshal())
res = self.send_raw_data(client, pkt.marshal())
self.refresh_lock.release()
return res
def send_raw_data(self, client: Hazelnut, data: bytes) -> int:
"""
Send a raw packet to a client.
"""
return self.socket.sendto(data, (str(client.address), client.port))
self.refresh_lock.acquire()
res = self.socket.sendto(data, (str(client.address), client.port))
self.refresh_lock.release()
return res
def receive_packet(self) -> Tuple[Packet, Hazelnut]:
"""
@ -225,7 +237,7 @@ class Squirrel(Hazelnut):
pkt = Packet.construct(DataTLV.construct(msg, self))
for hazelnut in list(self.activehazelnuts.values()):
self.send_packet(hazelnut, pkt)
self.send_packet(hazelnut[0], pkt)
def handle_mouse_click(self, y: int, x: int, attr: int) -> None:
"""
@ -488,8 +500,71 @@ class Squirrel(Hazelnut):
curses.LINES - 2, curses.COLS - 2)
self.refresh_lock.release()
def potential_to_contact(self) -> list:
"""
Returns a list of hazelnuts the squirrel should contact if it does
not have enough symmetric neighbours.
"""
self.refresh_lock.acquire()
res = []
lp = len(self.potentialhazelnuts)
val = list(self.potentialhazelnuts.values())
for i in range(min(lp, max(0,self.minNS-self.nbNS))) :
a = randint(0, lp-1)
res.append(val[a])
self.refresh_lock.release()
return res
def send_hello(self) -> None:
"""
Sends a long HelloTLV to all active neighbours.
"""
self.refresh_lock.acquire()
for hazelnut in self.activehazelnuts.values() :
htlv = HelloTLV().construct(16, self, hazelnut[0])
pkt = Packet().construct(htlv)
self.send_packet(hazelnut[0], pkt)
self.refresh_lock.release()
def verify_activity(self) -> None:
"""
All neighbours that have not sent a HelloTLV in the last 2
minutes are considered not active.
"""
self.refresh_lock.acquire()
val = list(self.activehazelnuts.values()) #create a copy because the dict size will change
for hazelnut in val :
if time.time()-hazelnut[1]>10: #2*60:
gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, "you did not talk to me")
pkt = Packet().construct(gatlv)
self.send_packet(hazelnut[0], pkt)
self.activehazelnuts.pop((str(hazelnut[0].address), hazelnut[0].port))
self.potentialhazelnuts[(str(hazelnut[0].address), hazelnut[0].port)] = hazelnut[0]
self.refresh_lock.release()
def send_neighbours(self) -> None:
"""
Update the number of symmetric neighbours and
send all neighbours NeighbourTLV
"""
self.refresh_lock.acquire()
nbNS = 0
#for hazelnut in self.activehazelnuts.values() :
# if time.time()-hazelnut[2]<2*60: #could send the same to all neighbour, but it means that neighbour A could receive a message with itself in it -> if the others do not pay attention, trouble
self.refresh_lock.release()
class Worm(Thread):
"""
The worm is the hazel listener.
@ -513,6 +588,43 @@ class Worm(Thread):
self.squirrel.refresh_history()
self.squirrel.refresh_input()
class HazelManager(Thread):
"""
A process to cleanly manage the squirrel's neighbours
"""
def __init__(self, squirrel: Squirrel, *args, **kwargs):
super().__init__(*args, **kwargs)
self.squirrel = squirrel
self.last_potential = 0
self.last_check = 0
self.last_neighbour = 0
htlv = HelloTLV().construct(8, self.squirrel)
pkt = Packet().construct(htlv)
self.hellopkt = pkt
def run(self) -> None:
while True:
#First part of neighbour management: ensure the squirrel has enough
#symmetric neighbours.
if time.time()-self.last_potential > 5:
to_contact = self.squirrel.potential_to_contact()
for hazel in to_contact :
self.squirrel.send_packet(hazel, self.hellopkt)
self.last_potential = time.time()
#Second part: send long HelloTLVs to neighbours every 30 seconds
if time.time()-self.last_check > 5: #30 :
self.squirrel.send_hello()
self.last_check = time.time()
#Third part: get rid of inactive neighbours
self.squirrel.verify_activity()
#Fourth part: verify symmetric neighbours and send NeighbourTLV every minute
class Message:
"""

View File

@ -162,32 +162,33 @@ class HelloTLV(TLV):
if not squirrel.is_active(sender) :
sender.id = self.source_id #The sender we are given misses an id
else :
timeHL = squirrel.activehazelnuts[(sender.address, sender.port)]
if self.is_long and dest_id == squirrel.id :
timeHL = squirrel.activehazelnuts[(str(sender.address), sender.port)]
if self.is_long and self.dest_id == squirrel.id :
timeHL = time.time()
#elif source_id != sender.id :
#That neighbour is lying about its ID
squirrel.remove_from_potential(sender.address, sender.port)
squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, timeH,\
timeHL, True]
squirrel.add_system_message("Aaaawwww, someone spoke to me and said me Hello smiling_face_with_"
+ (":3_hearts:" if self.is_long else "smiling_eyes:"))
squirrel.activehazelnuts[(str(sender.address), sender.port)] = [sender, timeH,\
timeHL]
squirrel.nbNS += 1
squirrel.add_system_message(f"Aaaawwww, {self.source_id} spoke to me and said me Hello "
+ ("long" if self.is_long else "short"))
@property
def is_long(self) -> bool:
return self.length == 16
@staticmethod
def construct(length: str, squirrel: Any) -> "HelloTLV":
def construct(length: int, squirrel: Any, destination: Any = None) -> "HelloTLV":
tlv = HelloTLV()
tlv.type = 2
tlv.source_id = squirrel.id if squirrel else 0
if length == "short":
if (destination is None) or destination.id == -1 or length == 8:
tlv.length = 8
tlv.dest_id = None #if the destination id is not known, or
#if the destination was not precised, send a short hello
else :
tlv.length = 16
tlv.dest_id = None
tlv.dest_id = destination.id
return tlv
@ -322,14 +323,14 @@ class AckTLV(TLV):
tlv.nonce = nonce
return tlv
class GoAwayTLV(TLV):
class GoAwayType(Enum):
class GoAwayType(Enum):
UNKNOWN = 0
EXIT = 1
TIMEOUT = 2
PROTOCOL_VIOLATION = 3
class GoAwayTLV(TLV):
type: int = 6
length: int
code: GoAwayType
@ -350,15 +351,15 @@ class GoAwayTLV(TLV):
def handle(self, squirrel: Any, sender: Any) -> None:
if squirrel.is_active(sender) :
squirrel.activehazelnuts.pop((sender.addess, sender.port))
squirrel.potentialhazelnuts[(sender.address, sender.port)] = sender
squirrel.add_system_message("Some told me that he went away : "+message)
squirrel.potentialhazelnuts[(str(sender.address), sender.port)] = sender
squirrel.add_system_message("Some told me that he went away : "+self.message)
@staticmethod
def construct(GAtype: GoAwayType, message: str) -> "Pad1TLV":
def construct(GAtype: GoAwayType, message: str) -> "GoAwayTLV":
tlv = GoAwayTLV()
tlv.type = 6
tlv.code = GAtype
tlv.message = message.encode("UTF-8")
tlv.message = message
tlv.length = 1 + len(tlv.message)
return tlv

View File

@ -2,10 +2,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import curses
import time
from argparse import ArgumentParser
from typing import Any
from .hazel import Hazelnut, Squirrel, Worm
from .hazel import Hazelnut, Squirrel, Worm, HazelManager
from .messages import Packet, HelloTLV
from .term_manager import TermManager
@ -74,7 +76,14 @@ class Squinnondation:
if instance.args.client_address and instance.args.client_port:
hazelnut = Hazelnut(address=instance.args.client_address, port=instance.args.client_port)
squirrel.activehazelnuts[(instance.args.client_address, instance.args.client_port)] = hazelnut
htlv = HelloTLV().construct(8, squirrel)
pkt = Packet().construct(htlv)
squirrel.send_packet(hazelnut, pkt)
## if squirrel.port != 8082:
## hazelnut = Hazelnut(address='::1', port=8082)
## squirrel.potentialhazelnuts['::1',8082] = hazelnut
Worm(squirrel).start()
HazelManager(squirrel).start()
squirrel.wait_for_key()