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:
		@@ -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:
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user