From e7822576dcabf0b0ea92c4f9f5c6236e803bcdb8 Mon Sep 17 00:00:00 2001 From: Phil Cummins Date: Wed, 20 Nov 2024 10:41:30 +0100 Subject: [PATCH] implements set_interface --- pyre/pyre.py | 3 +- pyre/pyre_node.py | 6 +++ pyre/zbeacon.py | 120 ++++++++++++++++++++++++++---------------- tests/test_zbeacon.py | 1 - 4 files changed, 84 insertions(+), 46 deletions(-) diff --git a/pyre/pyre.py b/pyre/pyre.py index 5d413d0..9c49315 100644 --- a/pyre/pyre.py +++ b/pyre/pyre.py @@ -117,7 +117,8 @@ def set_interface(self, value): """Set network interface for UDP beacons. If you do not set this, CZMQ will choose an interface for you. On boxes with several interfaces you should specify which one you want to use, or strange things can happen.""" - logging.debug("set_interface not implemented") #TODO + self.actor.send_unicode("SET INTERFACE", zmq.SNDMORE) + self.actor.send_unicode(value) # TODO: check args from zyre def set_endpoint(self, format, *args): diff --git a/pyre/pyre_node.py b/pyre/pyre_node.py index 276bd41..0b1d4ed 100644 --- a/pyre/pyre_node.py +++ b/pyre/pyre_node.py @@ -27,6 +27,7 @@ def __init__(self, ctx, pipe, outbox, *args, **kwargs): self.outbox = outbox # Outbox back to application self._terminated = False # API shut us down self._verbose = False # Log all traffic (logging module?) + self.interface_name = None # Network interface self.beacon_port = ZRE_DISCOVERY_PORT # Beacon port number self.interval = 0 # Beacon interval 0=default self.beacon = None # Beacon actor @@ -67,6 +68,9 @@ def start(self): if self._verbose: self.beacon.send_unicode("VERBOSE") + if self.interface_name: + self.beacon.send_unicode("SET INTERFACE", zmq.SNDMORE) + self.beacon.send_unicode(self.interface_name) # Our hostname is provided by zbeacon self.beacon.send_unicode("CONFIGURE", zmq.SNDMORE) @@ -160,6 +164,8 @@ def recv_api(self): self.beacon_port = int(request.pop(0)) elif command == "SET INTERVAL": self.interval = int(request.pop(0)) + elif command == "SET INTERFACE": + self.interface_name = request.pop(0).decode() #elif command == "SET ENDPOINT": # TODO: gossip start and endpoint setting # TODO: GOSSIP BIND, GOSSIP CONNECT diff --git a/pyre/zbeacon.py b/pyre/zbeacon.py index 1ae6afa..1c56c06 100644 --- a/pyre/zbeacon.py +++ b/pyre/zbeacon.py @@ -1,4 +1,3 @@ - # ====================================================================== # zbeacon - LAN discovery and presence # @@ -137,57 +136,85 @@ def prepare_udp(self): except socket.error: logger.exception("Initializing of {0} raised an exception".format(self.__class__.__name__)) - def _prepare_socket(self): - netinf = zhelper.get_ifaddrs() - - logger.debug("Available interfaces: {0}".format(netinf)) + def _try_interface(self, iface): - for iface in netinf: - # Loop over the interfaces and their settings to try to find the broadcast address. - # ipv4 only currently and needs a valid broadcast address - for name, data in iface.items(): - logger.debug("Checking out interface {0}.".format(name)) - # For some reason the data we need lives in the "2" section of the interface. - data_2 = data.get(2) + for name, data in iface.items(): + logger.debug("Checking out interface {0}.".format(name)) + # For some reason the data we need lives in the "2" section of the interface. + data_2 = data.get(2) - if not data_2: - logger.debug("No data_2 found for interface {0}.".format(name)) - continue + if not data_2: + logger.debug("No data_2 found for interface {0}.".format(name)) + return - address_str = data_2.get("addr") - netmask_str = data_2.get("netmask") + address_str = data_2.get("addr") + netmask_str = data_2.get("netmask") - if not address_str or not netmask_str: - logger.debug("Address or netmask not found for interface {0}.".format(name)) - continue + if not address_str or not netmask_str: + logger.debug("Address or netmask not found for interface {0}.".format(name)) + return - if isinstance(address_str, bytes): - address_str = address_str.decode("utf8") + if isinstance(address_str, bytes): + address_str = address_str.decode("utf8") - if isinstance(netmask_str, bytes): - netmask_str = netmask_str.decode("utf8") + if isinstance(netmask_str, bytes): + netmask_str = netmask_str.decode("utf8") - interface_string = "{0}/{1}".format(address_str, netmask_str) + interface_string = "{0}/{1}".format(address_str, netmask_str) - interface = ipaddress.ip_interface(u(interface_string)) + interface = ipaddress.ip_interface(u(interface_string)) - if interface.is_loopback: - logger.debug("Interface {0} is a loopback device.".format(name)) - continue + if interface.is_loopback: + logger.debug("Interface {0} is a loopback device.".format(name)) + return - if interface.is_link_local: - logger.debug("Interface {0} is a link-local device.".format(name)) - continue + if interface.is_link_local: + logger.debug("Interface {0} is a link-local device.".format(name)) + return - self.address = interface.ip - self.network_address = interface.network.network_address - self.broadcast_address = interface.network.broadcast_address - self.interface_name = name + self.address = interface.ip + self.network_address = interface.network.network_address + self.broadcast_address = interface.network.broadcast_address + self.interface_name = name if self.address: - break + return + + def _find_selected_interface(self, netinf): + for iface in netinf: + for name, data in iface.items(): + if name == self.interface_name: + return iface + return None - logger.debug("Finished scanning interfaces.") + def _prepare_socket(self): + + netinf = zhelper.get_ifaddrs() + + logger.debug("Available interfaces: {0}".format(netinf)) + + if self.interface_name: + logger.debug("Trying the selected interface: {0}".format(self.interface_name)) + + if len(self.interface_name) == 1 and self.interface_name.isdigit(): + logger.debug("Selected interface is a single digit, using as array index".format(self.interface_name)) + array_index = int(self.interface_name) + self._try_interface(netinf[array_index]) + else: + selected_interface = self._find_selected_interface(netinf) + if selected_interface is not None: + logger.debug("Found selected interface.") + self._try_interface(selected_interface) + + if not self.address: + logger.debug("Looping over interfaces.") + # Loop over the interfaces and their settings to try to find the broadcast address. + # ipv4 only currently and needs a valid broadcast address + for iface in netinf: + self._try_interface(iface) + if self.address: + break + logger.debug("Finished scanning interfaces.") if not self.address: self.network_address = ipaddress.IPv4Address(u('127.0.0.1')) @@ -217,6 +244,8 @@ def handle_pipe(self): if command == "VERBOSE": self.verbose = True + elif command == "SET INTERFACE": + self.interface_name = request.pop(0).decode() elif command == "CONFIGURE": port = struct.unpack('I', request.pop(0))[0] self.configure(port) @@ -269,20 +298,21 @@ def send_beacon(self): try: self.udpsock.sendto(self.transmit, (str(self.broadcast_address), self.port_nbr)) - + except OSError as e: - + # network down, just wait, it could come back up again. # socket call errors 50 and 51 relate to the network being - # down or unreachable, the recommended action to take is to + # down or unreachable, the recommended action to take is to # try again so we don't terminate in these cases. - if e.errno in [ENETDOWN, ENETUNREACH]: pass - + if e.errno in [ENETDOWN, ENETUNREACH]: + pass + # all other cases, we'll terminate else: logger.debug("Network seems gone, exiting zbeacon") self.terminated = True - + except socket.error: logger.debug("Network seems gone, exiting zbeacon") self.terminated = True @@ -317,12 +347,14 @@ def run(self): import zmq import struct import time + speaker = ZActor(zmq.Context(), ZBeacon) speaker.send_unicode("VERBOSE") speaker.send_unicode("CONFIGURE", zmq.SNDMORE) speaker.send(struct.pack("I", 9999)) speaker.send_unicode("PUBLISH", zmq.SNDMORE) import uuid + transmit = struct.pack('cccb16sH', b'Z', b'R', b'E', 1, uuid.uuid4().bytes, socket.htons(1300)) diff --git a/tests/test_zbeacon.py b/tests/test_zbeacon.py index 85d7e92..7e2b160 100644 --- a/tests/test_zbeacon.py +++ b/tests/test_zbeacon.py @@ -9,7 +9,6 @@ class ZBeaconTest(unittest.TestCase): def setUp(self, *args, **kwargs): - ctx = zmq.Context() ctx = zmq.Context() # two beacon frames self.transmit1 = struct.pack('cccb16sH', b'Z', b'R', b'E',