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:
parent
806287f834
commit
b7a495eb11
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue