From e4797b745af25ea0927af7bfc4077b3a3898df47 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 24 Dec 2020 13:07:24 +0100 Subject: [PATCH 01/18] Completed the handle function for the Hello, Neighbour and GoAway --- squinnondation/hazel.py | 35 ++++++++++++++++++++++++-------- squinnondation/messages.py | 29 +++++++++++++++++++------- squinnondation/squinnondation.py | 2 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index db99db9..3a531d5 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -68,18 +68,37 @@ class Squirrel(Hazelnut): curses.init_color(curses.COLOR_WHITE, 1000, 1000, 1000) for i in range(curses.COLOR_BLACK + 1, curses.COLOR_WHITE): curses.init_pair(i + 1, i, curses.COLOR_BLACK) - - self.hazelnuts = dict() + + #dictionnaries of neighbours + self.potentialhazelnuts = dict() + self.activehazelnuts = dict() #of the form [hazelnut, time of last + #hello, time of last long hello, is symetric] self.add_system_message(f"Listening on {self.address}:{self.port}") - + + def new_hazel(self, address: IPv6Address, port: int) -> Hazelnut: + """ + Returns a new hazelnut (with no id nor nickname) + """ + hazelnut = Hazelnut(address=address, port=port) + return hazelnut + + def is_active(self, hazel: Hazelnut) -> bool : + return (hazel.address, hazel.port) in self.activehazelnuts + + def is_potential(self, hazel: Hazelnut) -> bool : + return (hazel.address, hazel.port) in self.potentialhazelnuts + + def remove_from_potential(self, address: IPv6Address, port: int)-> None: + self.potentialhazelnuts.pop((address, port), None) + def find_hazelnut(self, address: str, port: int) -> Hazelnut: """ - Translate an address into a hazelnut, and store it in the list of the hazelnuts, ie. the neighbours. + Translate an address into a hazelnut. If this hazelnut does not exist, + creates a new hazelnut. """ - if (address, port) in self.hazelnuts: - return self.hazelnuts[(address, port)] + if (address, port) in self.activehazelnuts: + return self.activehazelnuts[(address, port)][0] hazelnut = Hazelnut(address=address, port=port) - self.hazelnuts[(address, port)] = hazelnut return hazelnut def send_packet(self, client: Hazelnut, pkt: Packet) -> int: @@ -205,7 +224,7 @@ class Squirrel(Hazelnut): self.add_message(msg) pkt = Packet.construct(DataTLV.construct(msg, self)) - for hazelnut in list(self.hazelnuts.values()): + for hazelnut in list(self.activehazelnuts.values()): self.send_packet(hazelnut, pkt) def handle_mouse_click(self, y: int, x: int, attr: int) -> None: diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 5ad2886..051ab58 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -6,6 +6,7 @@ from ipaddress import IPv6Address from enum import Enum import socket import sys +import time class TLV: @@ -143,7 +144,20 @@ class HelloTLV(TLV): return data def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Implement HelloTLV + timeH = time.time() + timeHL = None + if not squirrel.is_active(sender) : + sender.id = 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 = 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:")) @@ -171,9 +185,9 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Implement NeighbourTLV - squirrel.add_system_message("I have a friend!") - squirrel.add_system_message(f"Welcome {self.ip_address}:{self.port}!") + if not (ip_address,port) in squirrel.activehazelnuts and not (ip_address,port) in squirrel.potentialhazelnuts: + squirrel.potentialhazelnuts[(ip_address, port)] = squirrel.new_hazel(ip_address, port) + squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") class DataTLV(TLV): @@ -301,9 +315,10 @@ class GoAwayTLV(TLV): self.message.encode("UTF-8")[:self.length - 1] def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Implement GoAwayTLV - squirrel.add_system_message("Some told me that he went away. That's not very nice :( " - "I should send him some cake.") + 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) class WarningTLV(TLV): diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index 1769fbb..c1f900b 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -74,7 +74,7 @@ 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.hazelnuts[(instance.args.client_address, instance.args.client_port)] = hazelnut + squirrel.activehazelnuts[(instance.args.client_address, instance.args.client_port)] = hazelnut Worm(squirrel).start() squirrel.wait_for_key() From 806287f834d9ebeae423f588506e43e6449f4669 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 25 Dec 2020 21:22:52 +0100 Subject: [PATCH 02/18] Added construct functions to all TLVs that did not have one. --- squinnondation/messages.py | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 051ab58..5a68bec 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -83,6 +83,12 @@ class Pad1TLV(TLV): """ return 1 + @staticmethod + def construct() -> "Pad1TLV": + tlv = Pad1TLV() + tlv.type = 0 + return tlv + class PadNTLV(TLV): """ @@ -116,6 +122,13 @@ class PadNTLV(TLV): # TODO Add some easter eggs squirrel.add_system_message(f"I received {self.length} zeros, am I so a bag guy ? :cold_sweat:") + @staticmethod + def construct(length: int) -> "Pad1TLV": + tlv = PadNTLV() + tlv.type = 1 + tlv.length = length + tlv.mbz = b'0'*length + return tlv class HelloTLV(TLV): type: int = 2 @@ -147,7 +160,7 @@ class HelloTLV(TLV): timeH = time.time() timeHL = None if not squirrel.is_active(sender) : - sender.id = source_id #The sender we are given misses an id + 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 : @@ -165,6 +178,18 @@ class HelloTLV(TLV): def is_long(self) -> bool: return self.length == 16 + @staticmethod + def construct(length: str, squirrel: Any) -> "HelloTLV": + tlv = HelloTLV() + tlv.type = 2 + tlv.source_id = squirrel.id if squirrel else 0 + if length == "short": + tlv.length = 8 + else : + tlv.length = 16 + tlv.dest_id = None + return tlv + class NeighbourTLV(TLV): type: int = 3 @@ -189,6 +214,14 @@ class NeighbourTLV(TLV): squirrel.potentialhazelnuts[(ip_address, port)] = squirrel.new_hazel(ip_address, port) squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") + @staticmethod + def construct(address: IPv6Address, port: int) -> "Pad1TLV": + tlv = NeighbourTLV() + tlv.type = 3 + tlv.length = 18 #A priori... + tlv.ip_address = address + tlv.port = port + return tlv class DataTLV(TLV): type: int = 4 @@ -320,6 +353,14 @@ class GoAwayTLV(TLV): squirrel.potentialhazelnuts[(sender.address, sender.port)] = sender squirrel.add_system_message("Some told me that he went away : "+message) + @staticmethod + def construct(GAtype: GoAwayType, message: str) -> "Pad1TLV": + tlv = GoAwayTLV() + tlv.type = 6 + tlv.code = GAtype + tlv.message = message.encode("UTF-8") + tlv.length = 1 + len(tlv.message) + return tlv class WarningTLV(TLV): type: int = 7 From b7a495eb110d91aef5603cb0ec1a683ba6c8a593 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 27 Dec 2020 21:31:34 +0100 Subject: [PATCH 03/18] Implemented the first three phases of neighbour management (warning: for testing purposes, the delays before any action have been reduced) --- squinnondation/hazel.py | 128 +++++++++++++++++++++++++++++-- squinnondation/messages.py | 37 ++++----- squinnondation/squinnondation.py | 13 +++- 3 files changed, 150 insertions(+), 28 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 3a531d5..9a2f2cb 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -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: """ diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 5a68bec..d6e6312 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -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 diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index c1f900b..2a94668 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -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() From c4e03ed427ee5013cbfefd807e4ff89f2b5b4c37 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 28 Dec 2020 11:48:44 +0100 Subject: [PATCH 04/18] Implemented the fourth phase of neighbour management (the type of addresses sometimes changes in the cide, this should be addressed, there also remains a lot of debugging printing) --- squinnondation/hazel.py | 22 ++++++++++++++++------ squinnondation/messages.py | 8 ++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 9a2f2cb..4dc371a 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -10,7 +10,7 @@ import re import socket import time -from .messages import Packet, DataTLV, HelloTLV, GoAwayTLV, GoAwayType +from .messages import Packet, DataTLV, HelloTLV, GoAwayTLV, GoAwayType, NeighbourTLV class Hazelnut: @@ -85,7 +85,7 @@ class Squirrel(Hazelnut): """ Returns a new hazelnut (with no id nor nickname) """ - hazelnut = Hazelnut(address=address, port=port) + hazelnut = Hazelnut(address=str(address), port=port) return hazelnut def is_active(self, hazel: Hazelnut) -> bool : @@ -550,6 +550,7 @@ class Squirrel(Hazelnut): self.potentialhazelnuts[(str(hazelnut[0].address), hazelnut[0].port)] = hazelnut[0] self.refresh_lock.release() + def send_neighbours(self) -> None: """ @@ -559,9 +560,16 @@ class Squirrel(Hazelnut): 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 - + #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 + for hazelnut in self.activehazelnuts.values() : + if time.time()-hazelnut[2]<=2*60: + nbNS+=1 + ntlv = NeighbourTLV().construct(hazelnut[0].address,hazelnut[0].port) + pkt = Packet().construct(ntlv) + for destination in self.activehazelnuts.values() : + if destination[0].id != hazelnut[0].id : + self.send_packet(destination[0], pkt) + self.nbNS = nbNS self.refresh_lock.release() @@ -623,7 +631,9 @@ class HazelManager(Thread): self.squirrel.verify_activity() #Fourth part: verify symmetric neighbours and send NeighbourTLV every minute - + if time.time()-self.last_neighbour > 10: #60 : + self.squirrel.send_neighbours() + self.last_neighbour = time.time() class Message: diff --git a/squinnondation/messages.py b/squinnondation/messages.py index d6e6312..519a8d5 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -158,9 +158,9 @@ class HelloTLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: timeH = time.time() - timeHL = None if not squirrel.is_active(sender) : sender.id = self.source_id #The sender we are given misses an id + timeHL = time.time() else : timeHL = squirrel.activehazelnuts[(str(sender.address), sender.port)] if self.is_long and self.dest_id == squirrel.id : @@ -211,12 +211,12 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - if not (ip_address,port) in squirrel.activehazelnuts and not (ip_address,port) in squirrel.potentialhazelnuts: - squirrel.potentialhazelnuts[(ip_address, port)] = squirrel.new_hazel(ip_address, port) + if not (str(self.ip_address),self.port) in squirrel.activehazelnuts and not (str(self.ip_address),self.port) in squirrel.potentialhazelnuts: + squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = squirrel.new_hazel(self.ip_address, self.port) squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") @staticmethod - def construct(address: IPv6Address, port: int) -> "Pad1TLV": + def construct(address: IPv6Address, port: int) -> "NeighbourTLV": tlv = NeighbourTLV() tlv.type = 3 tlv.length = 18 #A priori... From 913f804f5ebdd7121ca21a328e29ed5c936490e6 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 28 Dec 2020 15:00:36 +0100 Subject: [PATCH 05/18] The address property of a hazel is now a string. The whole code has been changed accordingly. --- squinnondation/hazel.py | 22 +++++++++++----------- squinnondation/messages.py | 18 +++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 4dc371a..4d1806e 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -3,7 +3,7 @@ from datetime import datetime from random import randint from typing import Any, Tuple -from ipaddress import IPv6Address +#from ipaddress import IPv6Address from threading import Thread, RLock import curses import re @@ -30,7 +30,7 @@ class Hazelnut: # See https://fr.wikipedia.org/wiki/Adresse_IPv6_mappant_IPv4 address = "::ffff:" + socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0] - self.address = IPv6Address(address) + self.address = address #IPv6Address(address) self.port = port @@ -48,7 +48,7 @@ class Squirrel(Hazelnut): # Create UDP socket self.socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) # Bind the socket - self.socket.bind((str(self.address), self.port)) + self.socket.bind((self.address, self.port)) self.squinnondation = instance @@ -79,13 +79,13 @@ class Squirrel(Hazelnut): #to have. self.add_system_message(f"Listening on {self.address}:{self.port}") - self.add_system_message(f"I am {self.id}") + self.add_system_message(f"I am {self.id}}") - def new_hazel(self, address: IPv6Address, port: int) -> Hazelnut: + def new_hazel(self, address: str, port: int) -> Hazelnut: """ Returns a new hazelnut (with no id nor nickname) """ - hazelnut = Hazelnut(address=str(address), port=port) + hazelnut = Hazelnut(address=address, port=port) return hazelnut def is_active(self, hazel: Hazelnut) -> bool : @@ -94,8 +94,8 @@ class Squirrel(Hazelnut): def is_potential(self, hazel: Hazelnut) -> bool : return (hazel.address, hazel.port) in self.potentialhazelnuts - def remove_from_potential(self, address: IPv6Address, port: int)-> None: - self.potentialhazelnuts.pop((str(address), port), None) + def remove_from_potential(self, address: str, port: int)-> None: + self.potentialhazelnuts.pop((address, port), None) def find_hazelnut(self, address: str, port: int) -> Hazelnut: """ @@ -124,7 +124,7 @@ class Squirrel(Hazelnut): Send a raw packet to a client. """ self.refresh_lock.acquire() - res = self.socket.sendto(data, (str(client.address), client.port)) + res = self.socket.sendto(data, (client.address, client.port)) self.refresh_lock.release() return res @@ -546,8 +546,8 @@ class Squirrel(Hazelnut): 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.activehazelnuts.pop((hazelnut[0].address, hazelnut[0].port)) + self.potentialhazelnuts[(hazelnut[0].address, hazelnut[0].port)] = hazelnut[0] self.refresh_lock.release() diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 519a8d5..cdc7ff7 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import re from typing import Any, List, Optional -from ipaddress import IPv6Address +#from ipaddress import IPv6Address from enum import Enum import socket import sys @@ -162,12 +162,12 @@ class HelloTLV(TLV): sender.id = self.source_id #The sender we are given misses an id timeHL = time.time() else : - timeHL = squirrel.activehazelnuts[(str(sender.address), sender.port)] + timeHL = squirrel.activehazelnuts[(sender.address, sender.port)] if self.is_long and self.dest_id == squirrel.id : timeHL = time.time() squirrel.remove_from_potential(sender.address, sender.port) - squirrel.activehazelnuts[(str(sender.address), sender.port)] = [sender, timeH,\ + squirrel.activehazelnuts[(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 " @@ -195,13 +195,13 @@ class HelloTLV(TLV): class NeighbourTLV(TLV): type: int = 3 length: int - ip_address: IPv6Address + ip_address: str port: int def unmarshal(self, raw_data: bytes) -> None: self.type = raw_data[0] self.length = raw_data[1] - self.ip_address = IPv6Address(raw_data[2:18]) + self.ip_address = raw_data[2:18]#IPv6Address() self.port = int.from_bytes(raw_data[18:20], sys.byteorder) def marshal(self) -> bytes: @@ -211,12 +211,12 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - if not (str(self.ip_address),self.port) in squirrel.activehazelnuts and not (str(self.ip_address),self.port) in squirrel.potentialhazelnuts: - squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = squirrel.new_hazel(self.ip_address, self.port) + if not (self.ip_address,self.port) in squirrel.activehazelnuts and not (self.ip_address,self.port) in squirrel.potentialhazelnuts: + squirrel.potentialhazelnuts[(self.ip_address, self.port)] = squirrel.new_hazel(self.ip_address, self.port) squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") @staticmethod - def construct(address: IPv6Address, port: int) -> "NeighbourTLV": + def construct(address: str, port: int) -> "NeighbourTLV": tlv = NeighbourTLV() tlv.type = 3 tlv.length = 18 #A priori... @@ -351,7 +351,7 @@ 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[(str(sender.address), sender.port)] = sender + squirrel.potentialhazelnuts[(sender.address, sender.port)] = sender squirrel.add_system_message("Some told me that he went away : "+self.message) @staticmethod From c01fd697cd18bde6e46d6f35c1244f05ba2dfa4a Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 28 Dec 2020 18:33:57 +0100 Subject: [PATCH 06/18] Added a marker to active neighbours to remember if they are symmetric. Fixed a marshal function broken by the last commit --- squinnondation/hazel.py | 23 +++++++++++++++++++---- squinnondation/messages.py | 27 ++++++++++++++------------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 4d1806e..58bc52a 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -73,13 +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] + #hello, time of last long hello, is symmetric] 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}}") + self.add_system_message(f"I am {self.id}") def new_hazel(self, address: str, port: int) -> Hazelnut: """ @@ -561,14 +561,17 @@ class Squirrel(Hazelnut): nbNS = 0 #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 - for hazelnut in self.activehazelnuts.values() : - if time.time()-hazelnut[2]<=2*60: + for key, hazelnut in self.activehazelnuts.items() : + if time.time()-hazelnut[2]<=10 : #2*60 nbNS+=1 + self.activehazelnuts[key][3] = True ntlv = NeighbourTLV().construct(hazelnut[0].address,hazelnut[0].port) pkt = Packet().construct(ntlv) for destination in self.activehazelnuts.values() : if destination[0].id != hazelnut[0].id : self.send_packet(destination[0], pkt) + else: + self.activehazelnuts[key][3] = False self.nbNS = nbNS self.refresh_lock.release() @@ -634,6 +637,18 @@ class HazelManager(Thread): if time.time()-self.last_neighbour > 10: #60 : self.squirrel.send_neighbours() self.last_neighbour = time.time() + +class Inondator(Thread): + """ + A process to manage the inondation + """ + def __init__(self, squirrel: Squirrel, *args, **kwargs): + super().__init__(*args, **kwargs) + self.squirrel = squirrel + self.last_check = 0 + + def run(self) -> None: + while True: class Message: diff --git a/squinnondation/messages.py b/squinnondation/messages.py index cdc7ff7..7207532 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import re from typing import Any, List, Optional -#from ipaddress import IPv6Address +from ipaddress import IPv6Address from enum import Enum import socket import sys @@ -168,7 +168,7 @@ class HelloTLV(TLV): squirrel.remove_from_potential(sender.address, sender.port) squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, timeH,\ - timeHL] + timeHL, True] 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")) @@ -195,13 +195,13 @@ class HelloTLV(TLV): class NeighbourTLV(TLV): type: int = 3 length: int - ip_address: str + ip_address: IPv6Address port: int def unmarshal(self, raw_data: bytes) -> None: self.type = raw_data[0] self.length = raw_data[1] - self.ip_address = raw_data[2:18]#IPv6Address() + self.ip_address = IPv6Address(raw_data[2:18]) self.port = int.from_bytes(raw_data[18:20], sys.byteorder) def marshal(self) -> bytes: @@ -211,8 +211,8 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - if not (self.ip_address,self.port) in squirrel.activehazelnuts and not (self.ip_address,self.port) in squirrel.potentialhazelnuts: - squirrel.potentialhazelnuts[(self.ip_address, self.port)] = squirrel.new_hazel(self.ip_address, self.port) + if not (str(self.ip_address),self.port) in squirrel.activehazelnuts and not (str(self.ip_address),self.port) in squirrel.potentialhazelnuts: + squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = squirrel.new_hazel(str(self.ip_address), self.port) squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") @staticmethod @@ -220,7 +220,7 @@ class NeighbourTLV(TLV): tlv = NeighbourTLV() tlv.type = 3 tlv.length = 18 #A priori... - tlv.ip_address = address + tlv.ip_address = IPv6Address(address) tlv.port = port return tlv @@ -256,12 +256,13 @@ class DataTLV(TLV): TODO: Check that the tuple (sender_id, nonce) is unique to avoid duplicates. """ msg = self.data.decode('UTF-8') - if not squirrel.receive_message_from(msg, self.sender_id, self.nonce): - # The message was already received - return - + # Acknowledge the packet squirrel.send_packet(sender, Packet.construct(AckTLV.construct(self.sender_id, self.nonce))) + + if not squirrel.receive_message_from(msg, self.sender_id, self.nonce): + # The message was already received, do not print it + return nickname_match = re.match("(.*): (.*)", msg) if nickname_match is None: @@ -339,7 +340,7 @@ class GoAwayTLV(TLV): def unmarshal(self, raw_data: bytes) -> None: self.type = raw_data[0] self.length = raw_data[1] - self.code = GoAwayTLV.GoAwayType(raw_data[2]) + self.code = GoAwayType(raw_data[2]) self.message = raw_data[3:self.length - 1].decode("UTF-8") def marshal(self) -> bytes: @@ -350,7 +351,7 @@ class GoAwayTLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: if squirrel.is_active(sender) : - squirrel.activehazelnuts.pop((sender.addess, sender.port)) + squirrel.activehazelnuts.pop((sender.address, sender.port)) squirrel.potentialhazelnuts[(sender.address, sender.port)] = sender squirrel.add_system_message("Some told me that he went away : "+self.message) From 04f6fb60026976e04a941b7c8d86a31a742b18af Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 29 Dec 2020 13:48:55 +0100 Subject: [PATCH 07/18] Implemented the inundation, strange type error encountered when testing --- squinnondation/hazel.py | 89 ++++++++++++++++++++++++++++++++++++-- squinnondation/messages.py | 14 ++++-- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 58bc52a..1071a14 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -1,7 +1,7 @@ # Copyright (C) 2020 by eichhornchen, ÿnérant # SPDX-License-Identifier: GPL-3.0-or-later from datetime import datetime -from random import randint +from random import randint, uniform from typing import Any, Tuple #from ipaddress import IPv6Address from threading import Thread, RLock @@ -61,6 +61,7 @@ class Squirrel(Hazelnut): self.history = [] self.received_messages = dict() + self.recent_messages = dict() #of the form [Pkt(DataTLV), date of first reception, dict(neighbour, date of the next send, nb of times it has already been sent)] self.history_pad = curses.newpad(curses.LINES - 2, curses.COLS) self.input_pad = curses.newpad(1, curses.COLS) self.emoji_pad = curses.newpad(18, 12) @@ -75,7 +76,7 @@ class Squirrel(Hazelnut): self.activehazelnuts = dict() #of the form [hazelnut, time of last #hello, time of last long hello, is symmetric] self.nbNS = 0 - self.minNS = 3 #minimal number of symetric neighbours a squirrel needs + self.minNS = 3 #minimal number of symmetric neighbours a squirrel needs #to have. self.add_system_message(f"Listening on {self.address}:{self.port}") @@ -294,18 +295,93 @@ class Squirrel(Hazelnut): if self.last_line == len(self.history) - 2: self.last_line += 1 - def receive_message_from(self, msg: str, sender_id: int, nonce: int) -> bool: + def receive_message_from(self, tlv: DataTLV, msg: str, sender_id: int, nonce: int, relay: Hazelnut) -> bool: """ This method is called by a DataTLV, sent by a real person. This add the message in the history if not already done. Returns True iff the message was not already received previously. """ + if (sender_id, nonce) not in self.recent_messages: + #If it is a new message, add it to recent_messages + d = self.make_inundation_dict() + pkt = Packet().construct(tlv) + self.recent_messages[(sender_id, nonce)] = [pkt, time.time(), d] + + #in all cases, remove the sender from the list of neighbours to be inundated + self.remove_from_inundation(relay, sender_id, nonce) + if (sender_id, nonce) in self.received_messages: return False - self.add_message(msg) + self.add_message(msg) #for display purposes self.received_messages[(sender_id, nonce)] = Message(msg, sender_id, nonce) return True + + def make_inundation_dict(self) -> dict: + """ + Takes the activehazels dictionnary and returns a list of [hazel, date+random, 0] + """ + res = dict() + l = list(self.activehazelnuts.items()) + for key, hazel in l: + if hazel[3]: #Only if the neighbour is symmetric + next_send = uniform(1, 2) + res[key] = [hazel[0], time.time()+next_send, 0] + return res + + def remove_from_inundation(self, hazel: Hazelnut, sender_id: int, nonce: int) -> None: + """ + Remove the sender from the list of neighbours to be inundated + """ + if (sender_id, nonce) in self.recent_messages: + #If a peer is late in its acknowledgement, the absence of the previous if causes an error. + self.recent_messages[(sender_id, nonce)][2].pop((hazel.address, hazel.port), None) + + if not self.recent_messages[(sender_id, nonce)][2] : #If dictionnary is empty, remove the message + self.recent_messages.pop((sender_id, nonce), None) + + def clean_inundation(self): + """ + Remove messages which are overdue (older than 2 minutes) from the inundation dictionnary. + """ + self.refresh_lock.acquire() + + for key in self.recent_messages: + if time.time()-self.recent_messages[key][1] > 120: + self.recent_messages.pop(key) + + self.refresh_lock.release() + + def main_inundation(self): + """ + The main inundation function. + """ + self.refresh_lock.acquire() + + for key in self.recent_messages: + for key2 in self.recent_messages[key][2]: + if time.time()-self.recent_messages[key][2][key2][1] >= 0: + #send the packet if it is overdue + self.send_packet(self.recent_messages[key][2][key2][0], self.recent_messages[key][0]) + + a = self.recent_messages[key][2][key2][2] + + if a==5: #the neighbour is not reactive enough + gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, "No acknowledge") + pkt = Packet().construct(gatlv) + self.send_packet( + self.recent_messages[key][2][key2][0], pkt) + self.activehazelnuts.pop(key2) + self.potentialhazelnuts[key2] = self.recent_messages[key][2][key2][0] + self.recent_messages[key][2].pop(key2) + + #change the time until the next send + self.recent_messages[key][2][key2][2] = a+1 + next_send = uniform(2**(a-1), 2**a) + self.recent_messages[key][2][key2][1] = time.time()+next_send + + + self.refresh_lock.release() def add_system_message(self, msg: str) -> None: """ @@ -649,6 +725,11 @@ class Inondator(Thread): def run(self) -> None: while True: + #clean the dictionnary + self.squirrel.clean_inundation() + + #inundate + self.squirrel.main_inundation() class Message: diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 7207532..c9fd962 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -165,8 +165,11 @@ class HelloTLV(TLV): timeHL = squirrel.activehazelnuts[(sender.address, sender.port)] if self.is_long and self.dest_id == squirrel.id : timeHL = time.time() + + #Make sure the sender is not in the potential hazelnuts squirrel.remove_from_potential(sender.address, sender.port) - + + #Add entry to/actualize the active hazelnuts dictionnary squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, timeH,\ timeHL, True] squirrel.nbNS += 1 @@ -260,7 +263,7 @@ class DataTLV(TLV): # Acknowledge the packet squirrel.send_packet(sender, Packet.construct(AckTLV.construct(self.sender_id, self.nonce))) - if not squirrel.receive_message_from(msg, self.sender_id, self.nonce): + if not squirrel.receive_message_from(self, msg, self.sender_id, self.nonce, sender): # The message was already received, do not print it return @@ -312,8 +315,11 @@ class AckTLV(TLV): socket.htonl(self.nonce).to_bytes(4, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Implement AckTLV - squirrel.add_system_message("I received an AckTLV. I don't know what to do with it. Please implement me!") + """ + When an AckTLV is received, we know that we do not have to inundate that neighbour anymore. + """ + squirrel.add_system_message("I received an AckTLV") + squirrel.remove_from_inundation(sender, self.sender_id, self.nonce) @staticmethod def construct(sender_id: int, nonce: int) -> "AckTLV": From 7abaa7bcd483ce6f0241dd87a92c1c3524a075b1 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 29 Dec 2020 15:06:27 +0100 Subject: [PATCH 08/18] Repaired a few bugs in inundation (and the bug notes in the last commit) --- squinnondation/hazel.py | 44 ++++++++++++++++++-------------- squinnondation/messages.py | 9 ++++--- squinnondation/squinnondation.py | 3 ++- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 1071a14..d5fe396 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -301,7 +301,7 @@ class Squirrel(Hazelnut): This add the message in the history if not already done. Returns True iff the message was not already received previously. """ - if (sender_id, nonce) not in self.recent_messages: + if (sender_id, nonce) not in self.received_messages: #If it is a new message, add it to recent_messages d = self.make_inundation_dict() pkt = Packet().construct(tlv) @@ -333,12 +333,14 @@ class Squirrel(Hazelnut): """ Remove the sender from the list of neighbours to be inundated """ + self.refresh_lock.acquire() if (sender_id, nonce) in self.recent_messages: #If a peer is late in its acknowledgement, the absence of the previous if causes an error. self.recent_messages[(sender_id, nonce)][2].pop((hazel.address, hazel.port), None) if not self.recent_messages[(sender_id, nonce)][2] : #If dictionnary is empty, remove the message self.recent_messages.pop((sender_id, nonce), None) + self.refresh_lock.release() def clean_inundation(self): """ @@ -359,15 +361,22 @@ class Squirrel(Hazelnut): self.refresh_lock.acquire() for key in self.recent_messages: - for key2 in self.recent_messages[key][2]: - if time.time()-self.recent_messages[key][2][key2][1] >= 0: + k = list(self.recent_messages[key][2].keys()) + for key2 in k: + if time.time()>= self.recent_messages[key][2][key2][1] : + self.add_system_message(f"inundating {self.recent_messages[key][2][key2][0].id} with message {key}") + #send the packet if it is overdue self.send_packet(self.recent_messages[key][2][key2][0], self.recent_messages[key][0]) + + #change the time until the next send + a = self.recent_messages[key][2][key2][2] + self.recent_messages[key][2][key2][2] = a+1 + next_send = uniform(2**(a-1), 2**a) + self.recent_messages[key][2][key2][1] = time.time()+next_send - a = self.recent_messages[key][2][key2][2] - - if a==5: #the neighbour is not reactive enough - gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, "No acknowledge") + if self.recent_messages[key][2][key2][2]>=5: #the neighbour is not reactive enough + gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, f"{self.id} No acknowledge") pkt = Packet().construct(gatlv) self.send_packet( self.recent_messages[key][2][key2][0], pkt) @@ -375,12 +384,6 @@ class Squirrel(Hazelnut): self.potentialhazelnuts[key2] = self.recent_messages[key][2][key2][0] self.recent_messages[key][2].pop(key2) - #change the time until the next send - self.recent_messages[key][2][key2][2] = a+1 - next_send = uniform(2**(a-1), 2**a) - self.recent_messages[key][2][key2][1] = time.time()+next_send - - self.refresh_lock.release() def add_system_message(self, msg: str) -> None: @@ -618,7 +621,7 @@ class Squirrel(Hazelnut): 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: + if time.time()-hazelnut[1]>2*60: gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, "you did not talk to me") pkt = Packet().construct(gatlv) self.send_packet(hazelnut[0], pkt) @@ -638,7 +641,7 @@ class Squirrel(Hazelnut): nbNS = 0 #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 for key, hazelnut in self.activehazelnuts.items() : - if time.time()-hazelnut[2]<=10 : #2*60 + if time.time()-hazelnut[2]<=2*60: nbNS+=1 self.activehazelnuts[key][3] = True ntlv = NeighbourTLV().construct(hazelnut[0].address,hazelnut[0].port) @@ -694,7 +697,7 @@ class HazelManager(Thread): while True: #First part of neighbour management: ensure the squirrel has enough #symmetric neighbours. - if time.time()-self.last_potential > 5: + if time.time()-self.last_potential > 30: to_contact = self.squirrel.potential_to_contact() for hazel in to_contact : @@ -702,7 +705,8 @@ class HazelManager(Thread): self.last_potential = time.time() #Second part: send long HelloTLVs to neighbours every 30 seconds - if time.time()-self.last_check > 5: #30 : + if time.time()-self.last_check > 30 : + self.squirrel.add_system_message(f"I have {len(list(self.squirrel.activehazelnuts.values()))} friends") self.squirrel.send_hello() self.last_check = time.time() @@ -710,7 +714,7 @@ class HazelManager(Thread): self.squirrel.verify_activity() #Fourth part: verify symmetric neighbours and send NeighbourTLV every minute - if time.time()-self.last_neighbour > 10: #60 : + if time.time()-self.last_neighbour > 60 : self.squirrel.send_neighbours() self.last_neighbour = time.time() @@ -726,7 +730,9 @@ class Inondator(Thread): def run(self) -> None: while True: #clean the dictionnary - self.squirrel.clean_inundation() + if time.time()-self.last_check > 30 : + self.squirrel.clean_inundation() + self.last_check = time.time() #inundate self.squirrel.main_inundation() diff --git a/squinnondation/messages.py b/squinnondation/messages.py index c9fd962..5730c9f 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -162,7 +162,7 @@ class HelloTLV(TLV): sender.id = self.source_id #The sender we are given misses an id timeHL = time.time() else : - timeHL = squirrel.activehazelnuts[(sender.address, sender.port)] + timeHL = squirrel.activehazelnuts[(sender.address, sender.port)][2] if self.is_long and self.dest_id == squirrel.id : timeHL = time.time() @@ -173,8 +173,8 @@ class HelloTLV(TLV): squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, timeH,\ timeHL, True] 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")) + #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: @@ -216,7 +216,7 @@ class NeighbourTLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: if not (str(self.ip_address),self.port) in squirrel.activehazelnuts and not (str(self.ip_address),self.port) in squirrel.potentialhazelnuts: squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = squirrel.new_hazel(str(self.ip_address), self.port) - squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") + #squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") @staticmethod def construct(address: str, port: int) -> "NeighbourTLV": @@ -265,6 +265,7 @@ class DataTLV(TLV): if not squirrel.receive_message_from(self, msg, self.sender_id, self.nonce, sender): # The message was already received, do not print it + squirrel.add_system_message(f"I was inundated a message which I already knew {self.sender_id, self.nonce}") return nickname_match = re.match("(.*): (.*)", msg) diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index 2a94668..6da4af4 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -6,7 +6,7 @@ import time from argparse import ArgumentParser from typing import Any -from .hazel import Hazelnut, Squirrel, Worm, HazelManager +from .hazel import Hazelnut, Squirrel, Worm, HazelManager, Inondator from .messages import Packet, HelloTLV from .term_manager import TermManager @@ -86,4 +86,5 @@ class Squinnondation: Worm(squirrel).start() HazelManager(squirrel).start() + Inondator(squirrel).start() squirrel.wait_for_key() From ac249716f784942f71f4f625af5265c9f3945978 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 1 Jan 2021 17:56:48 +0100 Subject: [PATCH 09/18] Linting --- squinnondation/hazel.py | 219 ++++++++++++++++--------------- squinnondation/messages.py | 75 ++++++----- squinnondation/squinnondation.py | 7 +- 3 files changed, 154 insertions(+), 147 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index d5fe396..3d6575e 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -1,9 +1,10 @@ # Copyright (C) 2020 by eichhornchen, ÿnérant # SPDX-License-Identifier: GPL-3.0-or-later + from datetime import datetime from random import randint, uniform from typing import Any, Tuple -#from ipaddress import IPv6Address +# from ipaddress import IPv6Address from threading import Thread, RLock import curses import re @@ -30,7 +31,7 @@ class Hazelnut: # See https://fr.wikipedia.org/wiki/Adresse_IPv6_mappant_IPv4 address = "::ffff:" + socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0] - self.address = address #IPv6Address(address) + self.address = address # IPv6Address(address) self.port = port @@ -61,7 +62,8 @@ class Squirrel(Hazelnut): self.history = [] self.received_messages = dict() - self.recent_messages = dict() #of the form [Pkt(DataTLV), date of first reception, dict(neighbour, date of the next send, nb of times it has already been sent)] + self.recent_messages = dict() # of the form [Pkt(DataTLV), date of first reception, + # dict(neighbour, date of the next send, nb of times it has already been sent)] self.history_pad = curses.newpad(curses.LINES - 2, curses.COLS) self.input_pad = curses.newpad(1, curses.COLS) self.emoji_pad = curses.newpad(18, 12) @@ -70,34 +72,33 @@ class Squirrel(Hazelnut): curses.init_color(curses.COLOR_WHITE, 1000, 1000, 1000) for i in range(curses.COLOR_BLACK + 1, curses.COLOR_WHITE): curses.init_pair(i + 1, i, curses.COLOR_BLACK) - - #dictionnaries of neighbours + + # dictionnaries of neighbours self.potentialhazelnuts = dict() - self.activehazelnuts = dict() #of the form [hazelnut, time of last - #hello, time of last long hello, is symmetric] + self.activehazelnuts = dict() # of the form [hazelnut, time of last hello, + # time of last long hello, is symmetric] self.nbNS = 0 - self.minNS = 3 #minimal number of symmetric neighbours a squirrel needs - #to have. - + self.minNS = 3 # minimal number of symmetric 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: str, port: int) -> Hazelnut: """ Returns a new hazelnut (with no id nor nickname) """ hazelnut = Hazelnut(address=address, port=port) return hazelnut - - def is_active(self, hazel: Hazelnut) -> bool : + + def is_active(self, hazel: Hazelnut) -> bool: return (hazel.address, hazel.port) in self.activehazelnuts - - def is_potential(self, hazel: Hazelnut) -> bool : + + def is_potential(self, hazel: Hazelnut) -> bool: return (hazel.address, hazel.port) in self.potentialhazelnuts - - def remove_from_potential(self, address: str, port: int)-> None: + + def remove_from_potential(self, address: str, port: int) -> None: self.potentialhazelnuts.pop((address, port), None) - + def find_hazelnut(self, address: str, port: int) -> Hazelnut: """ Translate an address into a hazelnut. If this hazelnut does not exist, @@ -169,7 +170,7 @@ class Squirrel(Hazelnut): self.handle_key_pressed(key) - def handle_key_pressed(self, key: str) -> None: + def handle_key_pressed(self, key: str) -> None: # noqa: C901 """ Process the key press from the user. """ @@ -302,88 +303,87 @@ class Squirrel(Hazelnut): Returns True iff the message was not already received previously. """ if (sender_id, nonce) not in self.received_messages: - #If it is a new message, add it to recent_messages + # If it is a new message, add it to recent_messages d = self.make_inundation_dict() pkt = Packet().construct(tlv) self.recent_messages[(sender_id, nonce)] = [pkt, time.time(), d] - - #in all cases, remove the sender from the list of neighbours to be inundated + + # in all cases, remove the sender from the list of neighbours to be inundated self.remove_from_inundation(relay, sender_id, nonce) - + if (sender_id, nonce) in self.received_messages: return False - self.add_message(msg) #for display purposes + self.add_message(msg) # for display purposes self.received_messages[(sender_id, nonce)] = Message(msg, sender_id, nonce) return True - + def make_inundation_dict(self) -> dict: """ Takes the activehazels dictionnary and returns a list of [hazel, date+random, 0] """ res = dict() - l = list(self.activehazelnuts.items()) - for key, hazel in l: - if hazel[3]: #Only if the neighbour is symmetric + hazels = list(self.activehazelnuts.items()) + for key, hazel in hazels: + if hazel[3]: # Only if the neighbour is symmetric next_send = uniform(1, 2) - res[key] = [hazel[0], time.time()+next_send, 0] + res[key] = [hazel[0], time.time() + next_send, 0] return res - + def remove_from_inundation(self, hazel: Hazelnut, sender_id: int, nonce: int) -> None: """ Remove the sender from the list of neighbours to be inundated """ self.refresh_lock.acquire() if (sender_id, nonce) in self.recent_messages: - #If a peer is late in its acknowledgement, the absence of the previous if causes an error. + # If a peer is late in its acknowledgement, the absence of the previous if causes an error. self.recent_messages[(sender_id, nonce)][2].pop((hazel.address, hazel.port), None) - - if not self.recent_messages[(sender_id, nonce)][2] : #If dictionnary is empty, remove the message - self.recent_messages.pop((sender_id, nonce), None) + + if not self.recent_messages[(sender_id, nonce)][2]: # If dictionnary is empty, remove the message + self.recent_messages.pop((sender_id, nonce), None) self.refresh_lock.release() - - def clean_inundation(self): + + def clean_inundation(self) -> None: """ Remove messages which are overdue (older than 2 minutes) from the inundation dictionnary. """ self.refresh_lock.acquire() - + for key in self.recent_messages: - if time.time()-self.recent_messages[key][1] > 120: + if time.time() - self.recent_messages[key][1] > 120: self.recent_messages.pop(key) - + self.refresh_lock.release() - - def main_inundation(self): + + def main_inundation(self) -> None: """ The main inundation function. """ self.refresh_lock.acquire() - + for key in self.recent_messages: k = list(self.recent_messages[key][2].keys()) for key2 in k: - if time.time()>= self.recent_messages[key][2][key2][1] : + if time.time() >= self.recent_messages[key][2][key2][1]: self.add_system_message(f"inundating {self.recent_messages[key][2][key2][0].id} with message {key}") - - #send the packet if it is overdue + + # send the packet if it is overdue self.send_packet(self.recent_messages[key][2][key2][0], self.recent_messages[key][0]) - - #change the time until the next send + + # change the time until the next send a = self.recent_messages[key][2][key2][2] - self.recent_messages[key][2][key2][2] = a+1 - next_send = uniform(2**(a-1), 2**a) - self.recent_messages[key][2][key2][1] = time.time()+next_send - - if self.recent_messages[key][2][key2][2]>=5: #the neighbour is not reactive enough + self.recent_messages[key][2][key2][2] = a + 1 + next_send = uniform(2 ** (a - 1), 2 ** a) + self.recent_messages[key][2][key2][1] = time.time() + next_send + + if self.recent_messages[key][2][key2][2] >= 5: # the neighbour is not reactive enough gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, f"{self.id} No acknowledge") pkt = Packet().construct(gatlv) - self.send_packet( - self.recent_messages[key][2][key2][0], pkt) + self.send_packet(self.recent_messages[key][2][key2][0], pkt) self.activehazelnuts.pop(key2) self.potentialhazelnuts[key2] = self.recent_messages[key][2][key2][0] self.recent_messages[key][2].pop(key2) - + self.refresh_lock.release() def add_system_message(self, msg: str) -> None: @@ -579,82 +579,83 @@ 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]) - + + 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() : + + 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]>2*60: + + val = list(self.activehazelnuts.values()) # create a copy because the dict size will change + + for hazelnut in val: + if time.time() - hazelnut[1] > 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((hazelnut[0].address, hazelnut[0].port)) self.potentialhazelnuts[(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 - #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 - for key, hazelnut in self.activehazelnuts.items() : - if time.time()-hazelnut[2]<=2*60: - nbNS+=1 + + nb_ns = 0 + # 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 + for key, hazelnut in self.activehazelnuts.items(): + if time.time() - hazelnut[2] <= 2 * 60: + nb_ns += 1 self.activehazelnuts[key][3] = True - ntlv = NeighbourTLV().construct(hazelnut[0].address,hazelnut[0].port) + ntlv = NeighbourTLV().construct(hazelnut[0].address, hazelnut[0].port) pkt = Packet().construct(ntlv) - for destination in self.activehazelnuts.values() : - if destination[0].id != hazelnut[0].id : + for destination in self.activehazelnuts.values(): + if destination[0].id != hazelnut[0].id: self.send_packet(destination[0], pkt) else: self.activehazelnuts[key][3] = False - self.nbNS = nbNS - + self.nbNS = nb_ns + self.refresh_lock.release() - + + class Worm(Thread): """ The worm is the hazel listener. @@ -678,6 +679,7 @@ class Worm(Thread): self.squirrel.refresh_history() self.squirrel.refresh_input() + class HazelManager(Thread): """ A process to cleanly manage the squirrel's neighbours @@ -688,36 +690,37 @@ class HazelManager(Thread): 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 > 30: + # First part of neighbour management: ensure the squirrel has enough + # symmetric neighbours. + if time.time() - self.last_potential > 30: to_contact = self.squirrel.potential_to_contact() - - for hazel in 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 > 30 : + + # Second part: send long HelloTLVs to neighbours every 30 seconds + if time.time() - self.last_check > 30: self.squirrel.add_system_message(f"I have {len(list(self.squirrel.activehazelnuts.values()))} friends") self.squirrel.send_hello() self.last_check = time.time() - - #Third part: get rid of inactive neighbours + + # Third part: get rid of inactive neighbours self.squirrel.verify_activity() - - #Fourth part: verify symmetric neighbours and send NeighbourTLV every minute - if time.time()-self.last_neighbour > 60 : + + # Fourth part: verify symmetric neighbours and send NeighbourTLV every minute + if time.time() - self.last_neighbour > 60: self.squirrel.send_neighbours() self.last_neighbour = time.time() + class Inondator(Thread): """ A process to manage the inondation @@ -726,17 +729,17 @@ class Inondator(Thread): super().__init__(*args, **kwargs) self.squirrel = squirrel self.last_check = 0 - + def run(self) -> None: while True: - #clean the dictionnary - if time.time()-self.last_check > 30 : + # clean the dictionnary + if time.time() - self.last_check > 30: self.squirrel.clean_inundation() self.last_check = time.time() - - #inundate + + # inundate self.squirrel.main_inundation() - + class Message: """ diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 5730c9f..2b15993 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -123,13 +123,14 @@ class PadNTLV(TLV): squirrel.add_system_message(f"I received {self.length} zeros, am I so a bag guy ? :cold_sweat:") @staticmethod - def construct(length: int) -> "Pad1TLV": + def construct(length: int) -> "PadNTLV": tlv = PadNTLV() tlv.type = 1 tlv.length = length - tlv.mbz = b'0'*length + tlv.mbz = b'0' * length return tlv + class HelloTLV(TLV): type: int = 2 length: int @@ -157,24 +158,23 @@ class HelloTLV(TLV): return data def handle(self, squirrel: Any, sender: Any) -> None: - timeH = time.time() - if not squirrel.is_active(sender) : - sender.id = self.source_id #The sender we are given misses an id - timeHL = time.time() - else : - timeHL = squirrel.activehazelnuts[(sender.address, sender.port)][2] - if self.is_long and self.dest_id == squirrel.id : - timeHL = time.time() + time_h = time.time() + if not squirrel.is_active(sender): + sender.id = self.source_id # The sender we are given misses an id + time_hl = time.time() + else: + time_hl = squirrel.activehazelnuts[(sender.address, sender.port)][2] + if self.is_long and self.dest_id == squirrel.id: + time_hl = time.time() - #Make sure the sender is not in the potential hazelnuts + # Make sure the sender is not in the potential hazelnuts squirrel.remove_from_potential(sender.address, sender.port) - #Add entry to/actualize the active hazelnuts dictionnary - squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, timeH,\ - timeHL, True] + # Add entry to/actualize the active hazelnuts dictionnary + squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, time_h, time_hl, True] 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")) + # 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: @@ -187,11 +187,11 @@ class HelloTLV(TLV): tlv.source_id = squirrel.id if squirrel else 0 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.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 = destination.id + tlv.dest_id = destination.id return tlv @@ -214,19 +214,22 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - if not (str(self.ip_address),self.port) in squirrel.activehazelnuts and not (str(self.ip_address),self.port) in squirrel.potentialhazelnuts: - squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = squirrel.new_hazel(str(self.ip_address), self.port) - #squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") + if not (str(self.ip_address), self.port) in squirrel.activehazelnuts \ + and not (str(self.ip_address), self.port) in squirrel.potentialhazelnuts: + squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = \ + squirrel.new_hazel(str(self.ip_address), self.port) + # squirrel.add_system_message(f"New potential friend {self.ip_address}:{self.port}!") @staticmethod def construct(address: str, port: int) -> "NeighbourTLV": tlv = NeighbourTLV() tlv.type = 3 - tlv.length = 18 #A priori... + tlv.length = 18 # A priori... tlv.ip_address = IPv6Address(address) tlv.port = port return tlv + class DataTLV(TLV): type: int = 4 length: int @@ -259,10 +262,10 @@ class DataTLV(TLV): TODO: Check that the tuple (sender_id, nonce) is unique to avoid duplicates. """ msg = self.data.decode('UTF-8') - + # Acknowledge the packet squirrel.send_packet(sender, Packet.construct(AckTLV.construct(self.sender_id, self.nonce))) - + if not squirrel.receive_message_from(self, msg, self.sender_id, self.nonce, sender): # The message was already received, do not print it squirrel.add_system_message(f"I was inundated a message which I already knew {self.sender_id, self.nonce}") @@ -331,14 +334,15 @@ class AckTLV(TLV): tlv.nonce = nonce return tlv + class GoAwayType(Enum): - UNKNOWN = 0 - EXIT = 1 - TIMEOUT = 2 - PROTOCOL_VIOLATION = 3 + UNKNOWN = 0 + EXIT = 1 + TIMEOUT = 2 + PROTOCOL_VIOLATION = 3 + class GoAwayTLV(TLV): - type: int = 6 length: int code: GoAwayType @@ -357,20 +361,21 @@ class GoAwayTLV(TLV): self.message.encode("UTF-8")[:self.length - 1] def handle(self, squirrel: Any, sender: Any) -> None: - if squirrel.is_active(sender) : + if squirrel.is_active(sender): squirrel.activehazelnuts.pop((sender.address, sender.port)) squirrel.potentialhazelnuts[(sender.address, sender.port)] = sender - squirrel.add_system_message("Some told me that he went away : "+self.message) + squirrel.add_system_message("Some told me that he went away : " + self.message) @staticmethod - def construct(GAtype: GoAwayType, message: str) -> "GoAwayTLV": + def construct(ga_type: GoAwayType, message: str) -> "GoAwayTLV": tlv = GoAwayTLV() tlv.type = 6 - tlv.code = GAtype + tlv.code = ga_type tlv.message = message tlv.length = 1 + len(tlv.message) return tlv + class WarningTLV(TLV): type: int = 7 length: int diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index 6da4af4..80d9175 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -import time from argparse import ArgumentParser from typing import Any @@ -80,9 +79,9 @@ class Squinnondation: pkt = Packet().construct(htlv) squirrel.send_packet(hazelnut, pkt) -## if squirrel.port != 8082: -## hazelnut = Hazelnut(address='::1', port=8082) -## squirrel.potentialhazelnuts['::1',8082] = hazelnut + # if squirrel.port != 8082: + # hazelnut = Hazelnut(address='::1', port=8082) + # squirrel.potentialhazelnuts['::1', 8082] = hazelnut Worm(squirrel).start() HazelManager(squirrel).start() From 9ef3be22e85f5ecfc2dee3ed938b1c62ba93a894 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 1 Jan 2021 18:07:41 +0100 Subject: [PATCH 10/18] Sleep during threads to avoid loops that run infinitely --- squinnondation/hazel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 3d6575e..4039321 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -720,6 +720,9 @@ class HazelManager(Thread): self.squirrel.send_neighbours() self.last_neighbour = time.time() + # Avoid infinite loops + time.sleep(1) + class Inondator(Thread): """ @@ -740,6 +743,9 @@ class Inondator(Thread): # inundate self.squirrel.main_inundation() + # Avoid infinite loops + time.sleep(1) + class Message: """ From b70fbc75d18e579f63a3f9903b36413222bbc508 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 1 Jan 2021 18:14:22 +0100 Subject: [PATCH 11/18] Add debug option --- squinnondation/hazel.py | 4 ++-- squinnondation/squinnondation.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 4039321..00988ca 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -389,9 +389,9 @@ class Squirrel(Hazelnut): def add_system_message(self, msg: str) -> None: """ Add a new system log message. - TODO: Configure logging levels to ignore some messages. """ - return self.add_message(f"system: *{msg}*" if not self.squinnondation.no_markdown else f"system: {msg}") + if self.squinnondation.debug: + return self.add_message(f"system: *{msg}*" if not self.squinnondation.no_markdown else f"system: {msg}") def print_markdown(self, pad: Any, y: int, x: int, msg: str, bold: bool = False, italic: bool = False, underline: bool = False, strike: bool = False) -> int: diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index 80d9175..b0d959d 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -16,6 +16,7 @@ class Squinnondation: bind_port: int no_emoji: bool no_markdown: bool + debug: bool screen: Any def parse_arguments(self) -> None: @@ -32,6 +33,7 @@ class Squinnondation: help="Don't replace emojis.") parser.add_argument('--no-markdown', '-nm', action='store_true', help="Don't replace emojis.") + parser.add_argument('--debug', '-d', action='store_true', help="Debug mode.") self.args = parser.parse_args() if not (1024 <= self.args.bind_port <= 65535) or\ @@ -42,6 +44,7 @@ class Squinnondation: self.bind_port = self.args.bind_port self.no_emoji = self.args.no_emoji self.no_markdown = self.args.no_markdown + self.debug = self.args.debug @staticmethod def main() -> None: # pragma: no cover From 88ab6f5c76ca8f64afb501bf73e643d85611dea3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 1 Jan 2021 18:41:01 +0100 Subject: [PATCH 12/18] More security in TLV analysis --- squinnondation/messages.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 2b15993..a5304f7 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -120,7 +120,7 @@ class PadNTLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: # TODO Add some easter eggs - squirrel.add_system_message(f"I received {self.length} zeros, am I so a bag guy ? :cold_sweat:") + squirrel.add_system_message(f"I received {self.length} zeros, am I so a bad guy ? :cold_sweat:") @staticmethod def construct(length: int) -> "PadNTLV": @@ -201,6 +201,11 @@ class NeighbourTLV(TLV): ip_address: IPv6Address port: int + def validate_data(self) -> bool: + if not (1 <= self.port <= 65535): + raise ValueError(f"Invalid port received in NeighbourTLV: {self.port}") + return True + def unmarshal(self, raw_data: bytes) -> None: self.type = raw_data[0] self.length = raw_data[1] @@ -259,7 +264,6 @@ class DataTLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: """ A message has been sent. We log it. - TODO: Check that the tuple (sender_id, nonce) is unique to avoid duplicates. """ msg = self.data.decode('UTF-8') From a985dac4b0c5c85c7d8660bef5b23898f9a28cad Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 1 Jan 2021 19:55:30 +0100 Subject: [PATCH 13/18] Ctrl+C properly stop the program --- squinnondation/hazel.py | 39 ++++++++++++++++++++++++++++++++ squinnondation/messages.py | 4 ++-- squinnondation/squinnondation.py | 4 +--- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 00988ca..8ebec48 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -144,6 +144,23 @@ class Squirrel(Hazelnut): """ return self.socket.recvfrom(1024) + def start_threads(self) -> None: + """ + Start asynchronous threads. + """ + self.worm = Worm(self) + self.hazel_manager = HazelManager(self) + self.inondator = Inondator(self) + + # Kill subthreads when exitting the program + self.worm.setDaemon(True) + self.hazel_manager.setDaemon(True) + self.inondator.setDaemon(True) + + self.worm.start() + self.hazel_manager.start() + self.inondator.start() + def wait_for_key(self) -> None: """ Infinite loop where we are waiting for a key of the user. @@ -158,6 +175,10 @@ class Squirrel(Hazelnut): curses.LINES - 1, min(3 + len(self.nickname) + self.input_index, curses.COLS - 4)) except curses.error: continue + except KeyboardInterrupt: + # Exit the program and send GoAway to neighbours + self.leave() + return if key == curses.KEY_MOUSE: try: @@ -655,6 +676,24 @@ class Squirrel(Hazelnut): self.refresh_lock.release() + def leave(self) -> None: + """ + The program is exited. We send a GoAway to our neighbours, then close the program. + """ + self.refresh_lock.acquire() + + # Last inundation + self.main_inundation() + self.clean_inundation() + + # Broadcast a GoAway + gatlv = GoAwayTLV().construct(GoAwayType.EXIT, f"I am leaving! Good bye!") + pkt = Packet.construct(gatlv) + for hazelnut in self.activehazelnuts.values(): + self.send_packet(hazelnut[0], pkt) + + exit(0) + class Worm(Thread): """ diff --git a/squinnondation/messages.py b/squinnondation/messages.py index a5304f7..2cf0fb4 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -356,7 +356,7 @@ class GoAwayTLV(TLV): self.type = raw_data[0] self.length = raw_data[1] self.code = GoAwayType(raw_data[2]) - self.message = raw_data[3:self.length - 1].decode("UTF-8") + self.message = raw_data[3:2 + self.length].decode("UTF-8") def marshal(self) -> bytes: return self.type.to_bytes(1, sys.byteorder) + \ @@ -376,7 +376,7 @@ class GoAwayTLV(TLV): tlv.type = 6 tlv.code = ga_type tlv.message = message - tlv.length = 1 + len(tlv.message) + tlv.length = 1 + len(tlv.message.encode("UTF-8")) return tlv diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index b0d959d..e81d3c3 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -86,7 +86,5 @@ class Squinnondation: # hazelnut = Hazelnut(address='::1', port=8082) # squirrel.potentialhazelnuts['::1', 8082] = hazelnut - Worm(squirrel).start() - HazelManager(squirrel).start() - Inondator(squirrel).start() + squirrel.start_threads() squirrel.wait_for_key() From bb62669722c56591f468a646a3948a17c7736f78 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 4 Jan 2021 10:52:03 +0100 Subject: [PATCH 14/18] Removing the error raising in validate_data --- squinnondation/hazel.py | 9 ++++--- squinnondation/messages.py | 48 +++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 8ebec48..2e02863 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -707,11 +707,10 @@ class Worm(Thread): def run(self) -> None: while True: - try: - pkt, hazelnut = self.squirrel.receive_packet() - pkt.validate_data() - except ValueError as error: - self.squirrel.add_system_message("An error occurred while receiving a packet: {}".format(error)) + pkt, hazelnut = self.squirrel.receive_packet() + correct = pkt.validate_data() + if not correct : + self.squirrel.add_system_message("I received a incorrect packet") else: for tlv in pkt.body: tlv.handle(self.squirrel, hazelnut) diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 2cf0fb4..6503a1c 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -74,8 +74,7 @@ class Pad1TLV(TLV): return self.type.to_bytes(1, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Add some easter eggs - squirrel.add_system_message("For each byte in the packet that I received, you will die today. And eat cookies.") + squirrel.add_system_message("I received a Pad1TLV, how disapointing") def __len__(self) -> int: """ @@ -100,7 +99,8 @@ class PadNTLV(TLV): def validate_data(self) -> bool: if self.mbz != int(0).to_bytes(self.length, sys.byteorder): - raise ValueError("The body of a PadN TLV is not filled with zeros.") + return False + #raise ValueError("The body of a PadN TLV is not filled with zeros.") return True def unmarshal(self, raw_data: bytes) -> None: @@ -119,8 +119,8 @@ class PadNTLV(TLV): + self.mbz[:self.length] def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Add some easter eggs - squirrel.add_system_message(f"I received {self.length} zeros, am I so a bad guy ? :cold_sweat:") + if self.validate_data(): + squirrel.add_system_message(f"I received {self.length} zeros") @staticmethod def construct(length: int) -> "PadNTLV": @@ -139,8 +139,9 @@ class HelloTLV(TLV): def validate_data(self) -> bool: if self.length != 8 and self.length != 16: - raise ValueError("The length of a Hello TLV must be 8 for a short Hello, or 16 for a long Hello," - f"found {self.length}") + return False + #raise ValueError("The length of a Hello TLV must be 8 for a short Hello, or 16 for a long Hello," + # f"found {self.length}") return True def unmarshal(self, raw_data: bytes) -> None: @@ -158,6 +159,8 @@ class HelloTLV(TLV): return data def handle(self, squirrel: Any, sender: Any) -> None: + if not self.validate_data(self): + return None time_h = time.time() if not squirrel.is_active(sender): sender.id = self.source_id # The sender we are given misses an id @@ -421,16 +424,19 @@ class Packet: def validate_data(self) -> bool: """ Ensure that the packet is well-formed. - Raises a ValueError if the packet contains bad data. + Returns False if the packet contains bad data. """ if self.magic != 95: - raise ValueError("The magic code of the packet must be 95, found: {:d}".format(self.magic)) + return False + #raise ValueError("The magic code of the packet must be 95, found: {:d}".format(self.magic)) if self.version != 0: - raise ValueError("The version of the packet is not supported: {:d}".format(self.version)) + return False + #raise ValueError("The version of the packet is not supported: {:d}".format(self.version)) if not (0 <= self.body_length <= 1200): - raise ValueError("The body length of the packet is negative or too high. It must be between 0 and 1020," - "found: {:d}".format(self.body_length)) - return all(tlv.validate_data() for tlv in self.body) + return False + #raise ValueError("The body length of the packet is negative or too high. It must be between 0 and 1020," + # "found: {:d}".format(self.body_length)) + return True #all(tlv.validate_data() for tlv in self.body) @staticmethod def unmarshal(data: bytes) -> "Packet": @@ -446,15 +452,13 @@ class Packet: read_bytes = 0 while read_bytes < min(len(data) - 4, pkt.body_length): tlv_type = data[4 + read_bytes] - if not (0 <= tlv_type < len(TLV.tlv_classes())): - raise ValueError(f"TLV type is not supported: {tlv_type}") - tlv = TLV.tlv_classes()[tlv_type]() - tlv.unmarshal(data[4 + read_bytes:4 + read_bytes + pkt.body_length]) - pkt.body.append(tlv) - read_bytes += len(tlv) - - pkt.validate_data() - + if (0 <= tlv_type < len(TLV.tlv_classes())): + tlv = TLV.tlv_classes()[tlv_type]() + tlv.unmarshal(data[4 + read_bytes:4 + read_bytes + pkt.body_length]) + pkt.body.append(tlv) + read_bytes += len(tlv) + # Other TLV types are ignored + return pkt def marshal(self) -> bytes: From 122932e289bbdd7ae50171ee3adba3aa097e83ad Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 4 Jan 2021 11:02:58 +0100 Subject: [PATCH 15/18] Revert "Removing the error raising in validate_data" This reverts commit bb62669722c56591f468a646a3948a17c7736f78. --- squinnondation/hazel.py | 9 +++---- squinnondation/messages.py | 48 +++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 2e02863..8ebec48 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -707,10 +707,11 @@ class Worm(Thread): def run(self) -> None: while True: - pkt, hazelnut = self.squirrel.receive_packet() - correct = pkt.validate_data() - if not correct : - self.squirrel.add_system_message("I received a incorrect packet") + try: + pkt, hazelnut = self.squirrel.receive_packet() + pkt.validate_data() + except ValueError as error: + self.squirrel.add_system_message("An error occurred while receiving a packet: {}".format(error)) else: for tlv in pkt.body: tlv.handle(self.squirrel, hazelnut) diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 6503a1c..2cf0fb4 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -74,7 +74,8 @@ class Pad1TLV(TLV): return self.type.to_bytes(1, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - squirrel.add_system_message("I received a Pad1TLV, how disapointing") + # TODO Add some easter eggs + squirrel.add_system_message("For each byte in the packet that I received, you will die today. And eat cookies.") def __len__(self) -> int: """ @@ -99,8 +100,7 @@ class PadNTLV(TLV): def validate_data(self) -> bool: if self.mbz != int(0).to_bytes(self.length, sys.byteorder): - return False - #raise ValueError("The body of a PadN TLV is not filled with zeros.") + raise ValueError("The body of a PadN TLV is not filled with zeros.") return True def unmarshal(self, raw_data: bytes) -> None: @@ -119,8 +119,8 @@ class PadNTLV(TLV): + self.mbz[:self.length] def handle(self, squirrel: Any, sender: Any) -> None: - if self.validate_data(): - squirrel.add_system_message(f"I received {self.length} zeros") + # TODO Add some easter eggs + squirrel.add_system_message(f"I received {self.length} zeros, am I so a bad guy ? :cold_sweat:") @staticmethod def construct(length: int) -> "PadNTLV": @@ -139,9 +139,8 @@ class HelloTLV(TLV): def validate_data(self) -> bool: if self.length != 8 and self.length != 16: - return False - #raise ValueError("The length of a Hello TLV must be 8 for a short Hello, or 16 for a long Hello," - # f"found {self.length}") + raise ValueError("The length of a Hello TLV must be 8 for a short Hello, or 16 for a long Hello," + f"found {self.length}") return True def unmarshal(self, raw_data: bytes) -> None: @@ -159,8 +158,6 @@ class HelloTLV(TLV): return data def handle(self, squirrel: Any, sender: Any) -> None: - if not self.validate_data(self): - return None time_h = time.time() if not squirrel.is_active(sender): sender.id = self.source_id # The sender we are given misses an id @@ -424,19 +421,16 @@ class Packet: def validate_data(self) -> bool: """ Ensure that the packet is well-formed. - Returns False if the packet contains bad data. + Raises a ValueError if the packet contains bad data. """ if self.magic != 95: - return False - #raise ValueError("The magic code of the packet must be 95, found: {:d}".format(self.magic)) + raise ValueError("The magic code of the packet must be 95, found: {:d}".format(self.magic)) if self.version != 0: - return False - #raise ValueError("The version of the packet is not supported: {:d}".format(self.version)) + raise ValueError("The version of the packet is not supported: {:d}".format(self.version)) if not (0 <= self.body_length <= 1200): - return False - #raise ValueError("The body length of the packet is negative or too high. It must be between 0 and 1020," - # "found: {:d}".format(self.body_length)) - return True #all(tlv.validate_data() for tlv in self.body) + raise ValueError("The body length of the packet is negative or too high. It must be between 0 and 1020," + "found: {:d}".format(self.body_length)) + return all(tlv.validate_data() for tlv in self.body) @staticmethod def unmarshal(data: bytes) -> "Packet": @@ -452,13 +446,15 @@ class Packet: read_bytes = 0 while read_bytes < min(len(data) - 4, pkt.body_length): tlv_type = data[4 + read_bytes] - if (0 <= tlv_type < len(TLV.tlv_classes())): - tlv = TLV.tlv_classes()[tlv_type]() - tlv.unmarshal(data[4 + read_bytes:4 + read_bytes + pkt.body_length]) - pkt.body.append(tlv) - read_bytes += len(tlv) - # Other TLV types are ignored - + if not (0 <= tlv_type < len(TLV.tlv_classes())): + raise ValueError(f"TLV type is not supported: {tlv_type}") + tlv = TLV.tlv_classes()[tlv_type]() + tlv.unmarshal(data[4 + read_bytes:4 + read_bytes + pkt.body_length]) + pkt.body.append(tlv) + read_bytes += len(tlv) + + pkt.validate_data() + return pkt def marshal(self) -> bytes: From 17548ff73c1ffd624292a8c6cb388594b7c8401b Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 4 Jan 2021 18:26:06 +0100 Subject: [PATCH 16/18] Added a test to ensure oneself can not become neighbour of oneself (associated to the same adress) + remove one call to pkt.validate_data() --- squinnondation/hazel.py | 1 - squinnondation/messages.py | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 8ebec48..d11aeea 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -709,7 +709,6 @@ class Worm(Thread): while True: try: pkt, hazelnut = self.squirrel.receive_packet() - pkt.validate_data() except ValueError as error: self.squirrel.add_system_message("An error occurred while receiving a packet: {}".format(error)) else: diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 2cf0fb4..55b43c0 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -75,7 +75,7 @@ class Pad1TLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: # TODO Add some easter eggs - squirrel.add_system_message("For each byte in the packet that I received, you will die today. And eat cookies.") + squirrel.add_system_message("I received a Pad1TLV, how disapointing.") def __len__(self) -> int: """ @@ -120,7 +120,7 @@ class PadNTLV(TLV): def handle(self, squirrel: Any, sender: Any) -> None: # TODO Add some easter eggs - squirrel.add_system_message(f"I received {self.length} zeros, am I so a bad guy ? :cold_sweat:") + squirrel.add_system_message(f"I received {self.length} zeros.") @staticmethod def construct(length: int) -> "PadNTLV": @@ -173,7 +173,7 @@ class HelloTLV(TLV): # Add entry to/actualize the active hazelnuts dictionnary squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, time_h, time_hl, True] squirrel.nbNS += 1 - # squirrel.add_system_message(f"Aaaawwww, {self.source_id} spoke to me and said me Hello " + # squirrel.add_system_message(f"Aaaawwww, {self.source_id} spoke to me and said Hello " # + ("long" if self.is_long else "short")) @property @@ -219,6 +219,9 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: + if squirrel.address == str(self.ip_address) and squirrel.port == self.port : + #This case should never happen (and in our protocol it is not possible), but we include this test as a security measure. + return if not (str(self.ip_address), self.port) in squirrel.activehazelnuts \ and not (str(self.ip_address), self.port) in squirrel.potentialhazelnuts: squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = \ @@ -271,7 +274,7 @@ class DataTLV(TLV): squirrel.send_packet(sender, Packet.construct(AckTLV.construct(self.sender_id, self.nonce))) if not squirrel.receive_message_from(self, msg, self.sender_id, self.nonce, sender): - # The message was already received, do not print it + # The message was already received, do not print it on screen squirrel.add_system_message(f"I was inundated a message which I already knew {self.sender_id, self.nonce}") return From 5422d12ed1e83412ed73b7c31c066d43c002b912 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 4 Jan 2021 21:27:55 +0100 Subject: [PATCH 17/18] Added a latex Readme --- Readme.tex | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Readme.tex diff --git a/Readme.tex b/Readme.tex new file mode 100644 index 0000000..942a26c --- /dev/null +++ b/Readme.tex @@ -0,0 +1,159 @@ +\documentclass[a4paper,10pt,oneside]{report} + + +%\usepackage{concmath} +\usepackage[utf8]{inputenc} % codage latin1 +\usepackage[T1]{fontenc} % codage des fonts +\usepackage[francais]{babel} % typographie et césures francaises + +\usepackage{lastpage} + +\usepackage{amsthm} %pour qed +\usepackage{amsmath} %pour \dfrac +\usepackage{amssymb,mathrsfs} % amsfonts + +\usepackage{graphicx} + +% \usepackage[colorlinks]{hyperref} + + +\usepackage{tikz} % L A TEX + +\usepackage[margin=1.0cm,top=2.5cm]{geometry} + +\usepackage{fancyhdr} % pour les en-têtes et les pieds de page : APRES LA GEOMETRIE DE LA PAGE +\pagestyle{fancy} % 6 par défaut 3 en tête et 3 pied +% \pagestyle{empty} % supprime les num\'eros de page qui sont automatiques + +% \usepackage{xcolor} +% \definecolor{gris}{gray}{0.9} + + + +\setlength{\parskip}{0.15cm} % hauteur entre chaque paragraphe +\setlength{\parindent}{0.0cm} % indentation de paragraphe + + +%********************************************************************************* +%******************************************************************************** +% Macros +%******************************************************************************** +%********************************************************************************* + +\newcommand{\R}{{\mathbb R}} +\newcommand{\Q}{{\mathbb Q}} +\newcommand{\C}{{\mathbb C}} +\newcommand{\Z}{\mathbb Z} +\newcommand{\dd}{\displaystyle} +\newcommand{\Id}{\mathrm{[}\! \mathrm{[}} +\newcommand{\If}{\mathrm{]}\! \mathrm{]}} +\newcommand{\N}{{\mathbb N}} +\newcommand{\E}{{\mathbf E}} +\newcommand{\Hx}{{\mathbf H(X)}} +\newcommand{\Prob}{{\mathbb P}} +\newcommand{\V}{{\mathbf V}} +\newcommand{\X}{{\mathcal X}} + +\newcommand{\be}{\begin{enumerate}} +\newcommand{\ee}{\end{enumerate}} +\newcommand{\bi}{\begin{itemize}} +\newcommand{\ei}{\end{itemize}} +\newcommand{\ba}{\begin{array}} +\newcommand{\ea}{\end{array}} +\newcommand{\ite}{\item[$\bullet$]} +\newcommand{\ssi}{\Longleftrightarrow} +\newcommand{\Card}{\mathop{ \mathrm{Card}}} + + +\everymath{\displaystyle} %comme son nom l'indique + +%********************************************************************************* +%********************************************************************************* +% Document +%********************************************************************************* +%********************************************************************************* +% changement de la numerotation +\setcounter{secnumdepth}{5} +% \renewcommand{\thechapter}{\Alph{chapter}} +\renewcommand{\thechapter}{\arabic{chapter}} +% \renewcommand{\thesection}{\Roman{section})} +\renewcommand{\thesection}{ } +\renewcommand{\thesubsection}{\alph{subsection})} +\renewcommand{\thesubsubsection}{$\bullet$ } +% \renewcommand{\thesubsubsection}{\thesubsection\arabic{subsubsection}.} +\renewcommand{\theparagraph}{\roman{paragraph})} + + +\begin{document} + + +\setlength{\headheight}{13.0pt} + +\fancyhead[L]{DEPRES Mathilde-D'ANELLO Yohann} +% \renewcommand{\headrulewidth}{0pt} + +\fancyhead[C]{{\sf Projet réseau}} + + +\fancyhead[R]{page {\thepage} / \pageref{LastPage} } +% \pagestyle{empty} % 6 par défaut 3 en tête et 3 pied +\fancyfoot{} + + + +\vspace{0.0cm} + +\section{Introduction} + +Notre projet est rédigé en Python, et fonctionne dans les versions plus récentes que Python 3.7. Il comprend une interface graphique, implémentée pour nous amuser et rendre les tests plus aisés. L'interface graphique utilise le module curses de Python. Le module curses (et donc le projet en entier) ne fonctionne que si le projet est exécuté dans un terminal. Le projet supporte les encodages markdown (gras, italique, souligné ...) et l'utilisation d'emojis à partir du moment où le terminal les supporte. + +\subsection{Lancer une instance du projet} + +Pour lancer une instance du projet, il faut se placer dans le répertoire racine du projet, et exécuter + +> python3 main.py [ options]. + +Les options sont: +\begin{itemize} + \item[$\bullet$] \textbf{- -client\_address } : pour spécifier l'adresse d'un premier voisin nécessaire à l'insertion du nouveau pair dans le réseau. + \item[$\bullet$] \textbf{- -client\_port } : pour spécifier le port sur lequel écouter le premier voisin. + \item[$\bullet$] \textbf{-h} : pour obtenir l'aide + \item[$\bullet$] \textbf{- -debug} : pour activer l'affichage des messages systèmes, par exemple lorsqu'on reconnait un nouveau voisin, ou qu'on reçoit certains TLVs. + \item[$\bullet$] \textbf{- -no-emoji} : une option graphique si on ne veut pas afficher d'emoji. + \item[$\bullet$] \textbf{- -no-markdown} : une option graphique si on ne veut pas utiliser les encodages markdown. +\end{itemize} + + +\subsection{Architecture du projet} + +Le projet consiste en 4 fichiers de code, et un fichier main.py, qui permet de lancer une instance du projet. + +Le fichier term\_manager.py permet d'initialiser l'utilisation du terminal par curses, il est sans grand intérêt pour la partie réseau du projet. + +Le fichier squinnondation.py contient le parseur d'arguments qu'on utilise pour récupérer les adresses de l'hôte et du premier voisin donnés par l'utilisateur. Il se conclut par le lancement de plusieurs threads qui constituent le client MIRC en lui-même, et dont les classes sont définis dans les deux derniers fichiers. + +Le fichier messages.py contient les définitions de tout les TLVs, qui sont définis comme des classes python. + +Le fichier hazel.py contient les définitions de la classe voisin, la classe de l'hôte ainsi que les classes du listener, du manager des voisins et de l'inondateur. Ils contient aussi l'actualisation de l'affichage. + +\section{Choix techniques} +\subsection{Gestion des TLVs} + +La classe \textbf{TLV} représente l'abstraction d'un TLV. Elle est sous-classée en chacun des types individuels de TLV (Pad1TLV, PadNTLV, ...). Chaque classe de TLV est équipée d'une fonction marshall qui transforme un objet de la classe en un tableau d'octets prêt à être envoyé, et d'une fonction unmarshall, qui transforme des octets en un objet de la classe. + +Chaque classe de TLV possède également une fonction construct, qui permet au programme de construire un objet de la classe, et d'une fonction handle, qui indiquee ce qui doit être fait quand ce type de TLV est reçu. Pour des raisons de sécurité, certaines classes sont équipées d'une fonction validate\_data, qui s'assure que certaines propriétés du TLV concordent, par exemple sa longueur annoncée et sa longueur réelle, et qui lancent une erreur si ça n'est pas le cas. Cela pourrait permettre en particulier d'indentifier des pairs malicieux qui envoient des TLVs malformés. + +\subsection{Inondation} + +Les messages récents sont placés dans un dictionnaire indexé par les paires (Id de l'émetteur, nonce). +L'inondation est effectuée dans un thread dédié. + +-> compteur séquentiel. + +\subsection{Gestion des voisins} + +Comme demandé par l'énoncé, les voisins sont placés dans une table des voisins actifs, qui est un dictionnaire de liste [objet voisin, date du dernier Hello reçu, date du dernier Hello long reçu, ce voisin est-il symétrique], indexé par les couples (addresse IP, port). Chaque pair possède aussi un dictionnaire des voisins potentiels. + +La gestion des voisins est effectuée dans un thread dédié, qui vérifie régulièrement si les voisins sont symétriques, envoie des HelloTLV longs aux voisins actifs et des hello TLVs court à des voisins potentiels si c'est néxcessaire. + +\end{document} From f85b52524a334d666f4311bd824406804dcb4fd2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 5 Jan 2021 16:03:16 +0100 Subject: [PATCH 18/18] Linting --- squinnondation/hazel.py | 2 +- squinnondation/messages.py | 7 ++++--- squinnondation/squinnondation.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index d11aeea..7b173f0 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -687,7 +687,7 @@ class Squirrel(Hazelnut): self.clean_inundation() # Broadcast a GoAway - gatlv = GoAwayTLV().construct(GoAwayType.EXIT, f"I am leaving! Good bye!") + gatlv = GoAwayTLV().construct(GoAwayType.EXIT, "I am leaving! Good bye!") pkt = Packet.construct(gatlv) for hazelnut in self.activehazelnuts.values(): self.send_packet(hazelnut[0], pkt) diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 55b43c0..d6157a4 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -219,9 +219,10 @@ class NeighbourTLV(TLV): self.port.to_bytes(2, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - if squirrel.address == str(self.ip_address) and squirrel.port == self.port : - #This case should never happen (and in our protocol it is not possible), but we include this test as a security measure. - return + if squirrel.address == str(self.ip_address) and squirrel.port == self.port: + # This case should never happen (and in our protocol it is not possible), + # but we include this test as a security measure. + return if not (str(self.ip_address), self.port) in squirrel.activehazelnuts \ and not (str(self.ip_address), self.port) in squirrel.potentialhazelnuts: squirrel.potentialhazelnuts[(str(self.ip_address), self.port)] = \ diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index e81d3c3..22c9999 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -5,7 +5,7 @@ import curses from argparse import ArgumentParser from typing import Any -from .hazel import Hazelnut, Squirrel, Worm, HazelManager, Inondator +from .hazel import Hazelnut, Squirrel from .messages import Packet, HelloTLV from .term_manager import TermManager