diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d9568e4..cb5e0fd 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.4 +current_version = 0.3.5 commit = True tag = True diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8adf171..12ea440 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: pip install build python -m build --sdist . - name: Upload source package - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: distribution path: dist/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4569fd1..300dd5e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: pip install build python -m build --sdist . - name: Upload source package - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: distribution path: dist/ diff --git a/pyre/__init__.py b/pyre/__init__.py index 6531e8d..9887279 100644 --- a/pyre/__init__.py +++ b/pyre/__init__.py @@ -1,5 +1,5 @@ __all__ = ['pyre', 'zbeacon', 'zhelper'] -__version__ = '0.3.4' +__version__ = '0.3.5' __version_info__ = tuple(int(v) for v in __version__.split('.')) from .pyre import Pyre 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..010fabe 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 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..adebe63 100644 --- a/pyre/zbeacon.py +++ b/pyre/zbeacon.py @@ -1,4 +1,3 @@ - # ====================================================================== # zbeacon - LAN discovery and presence # @@ -36,28 +35,28 @@ logger = logging.getLogger(__name__) INTERVAL_DFLT = 1.0 -BEACON_MAX = 255 # Max size of beacon data +BEACON_MAX = 255 # Max size of beacon data MULTICAST_GRP = '225.25.25.25' -ENETDOWN = 50 #socket error, network is down -ENETUNREACH = 51 #socket error, network unreachable +ENETDOWN = 50 # socket error, network is down +ENETUNREACH = 51 # socket error, network unreachable class ZBeacon(object): def __init__(self, ctx, pipe, *args, **kwargs): - self.ctx = ctx # ZMQ context - self.pipe = pipe # Actor command pipe + self.ctx = ctx # ZMQ context + self.pipe = pipe # Actor command pipe self.udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - # UDP socket for send/recv - self.port_nbr = 0 # UDP port number we work on - self.interval = INTERVAL_DFLT # Beacon broadcast interval - self.ping_at = 0 # Next broadcast time - self.transmit = None # Beacon transmit data - self.filter = b"" # Beacon filter data + # UDP socket for send/recv + self.port_nbr = 0 # UDP port number we work on + self.interval = INTERVAL_DFLT # Beacon broadcast interval + self.ping_at = 0 # Next broadcast time + self.transmit = None # Beacon transmit data + self.filter = b"" # Beacon filter data - self.terminated = False # Did caller ask us to quit? - self.verbose = False # Verbose logging enabled? - self.hostname = "" # Saved host name + self.terminated = False # Did caller ask us to quit? + self.verbose = False # Verbose logging enabled? + self.hostname = "" # Saved host name self.address = None self.network_address = None @@ -137,57 +136,78 @@ 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() + def _try_interface(self, iface): - logger.debug("Available interfaces: {0}".format(netinf)) + 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 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) + if not data_2: + logger.debug("No data_2 found for interface {0}.".format(name)) + return - if not data_2: - logger.debug("No data_2 found for interface {0}.".format(name)) - continue + 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)) + return - if not address_str or not netmask_str: - logger.debug("Address or netmask not found for interface {0}.".format(name)) - continue + 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)) + return - if interface.is_loopback: - logger.debug("Interface {0} is a loopback device.".format(name)) - continue + if interface.is_link_local: + logger.debug("Interface {0} is a link-local device.".format(name)) + return - if interface.is_link_local: - logger.debug("Interface {0} is a link-local device.".format(name)) - continue - - 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)) + selected_interface = self._find_selected_interface(netinf) + if selected_interface is not None: + 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')) @@ -210,13 +230,15 @@ def handle_pipe(self): request = self.pipe.recv_multipart() command = request.pop(0).decode('UTF-8') if not command: - return -1 # Interrupted + return -1 # Interrupted if self.verbose: logger.debug("zbeacon: API command={0}".format(command)) 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 +291,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 +340,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/setup.py b/setup.py index 14e4cda..73662c5 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='zeromq-pyre', - version='0.3.4', + version='0.3.5', description='Python ZRE implementation', author='Arnaud Loonstra', author_email='arnaud@sphaero.org', diff --git a/tests/test_pyre.py b/tests/test_pyre.py index 5b656c0..69b5f03 100644 --- a/tests/test_pyre.py +++ b/tests/test_pyre.py @@ -163,6 +163,7 @@ def test_zfinal(self): global inst_count inst_count = 1 self.assertTrue(True) + # end test_zfinal # end PyreTest 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',