Exemple #1
0
 def __init__(self):
     """Constructor."""
     super(AvahiLocationSource, self).__init__()
     self.connection = GEventDBusConnection(DBUS_BUS_SYSTEM)
     handler = AvahiHandler(self._avahi_event)
     self.connection.add_handler(handler)
     self.logger = logging.getLogger(__name__)
     self.callbacks = []
     self.neighbors = {}
     self.addresses = {}
     self._browser = None
     self._entry_groups = {}
Exemple #2
0
 def setUpClass(cls):
     super(TestMessageName, cls).setUpClass()
     cls.server_name = "org.tdbus.Test"
     handler = EchoHandlerInherit()
     conn = GEventDBusConnection(DBUS_BUS_SESSION)
     conn.register_name(cls.server_name)
     conn.add_handler(handler)
     cls.client = GEventDBusConnection(DBUS_BUS_SESSION)
Exemple #3
0
 def __init__(self):
     """Constructor."""
     super(AvahiLocationSource, self).__init__()
     self.connection = GEventDBusConnection(DBUS_BUS_SYSTEM)
     handler = AvahiHandler(self._avahi_event)
     self.connection.add_handler(handler)
     self.logger = logging.getLogger(__name__)
     self.callbacks = []
     self.neighbors = {}
     self.addresses = {}
     self._browser = None
     self._entry_groups = {}
Exemple #4
0
 def setUpClass(cls):
     super(TestMessageDecorated, cls).setUpClass()
     handler = EchoHandlerDecorator()
     conn = GEventDBusConnection(DBUS_BUS_SESSION)
     conn.add_handler(handler)
     cls.server_name = conn.get_unique_name()
     cls.client = GEventDBusConnection(DBUS_BUS_SESSION)
Exemple #5
0
    def setUpClass(cls):
        super(TestMessageSignalMatched, cls).setUpClass()
        cls.server_name = "org.tdbus.Test"

        def signal_handler_f(message):
            logging.getLogger('tdbus').info(message)
            cls.last_message = message

        handler = EchoHandlerInherit(signal_handler_f)
        conn = GEventDBusConnection(DBUS_BUS_SESSION)
        conn.add_handler(handler)
        conn.subscribe_to_signals()
        cls.client = GEventDBusConnection(DBUS_BUS_SESSION)
Exemple #6
0
            xml = """<?xml version="1.0" encoding="UTF-8"?>
    <node name="com/example/TDBus">
            <interface name="com.example.Hello">
                    <method name="HelloMethod">
                            <arg type="s" name="somestring" direction="in" />
                            <arg type="i" name="someint" direction="in" />
                            <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
                    </method>
                    <!-- Add more methods/signals if you want -->
            </interface>
    </node>"""

        self.set_response("s", [xml])


conn = GEventDBusConnection(DBUS_BUS_SESSION)
handler = GEventHandler()
conn.add_handler(handler)

print('Listening for signals, with gevent dispatcher.')
print('In another terminal, issue:')
print()
print(
    '  $ dbus-send --session --type=signal --dest={} /com/example/TDBus com.example.Hello.Hello'
    .format(conn.get_unique_name()))
print(
    '  $ dbus-send --session --print-reply --type=method_call --dest={} /com/example/TDBus com.example.Hello.HelloMethod'
    .format(conn.get_unique_name()))
print()
print('Press CTRL-c to exit.')
print()
Exemple #7
0
class AvahiLocationSource(ZeroconfLocationSource):
    """Avahi Zeroconf location source.
    
    This location source provides loation services for a local network
    using DNS-SD. This source is for freedesktop like platforms and uses
    Avahi via its D-BUS interface.

    DNS-SD is used in the following way for vault discovery:

    1. The Bluepass service is registered as a PTR record under:

        _bluepass._tcp.local

    2. The previous PTR record will resolve to a list of SRV and TXT records.
       Instead of using the vault UUID as the service name, this uses the
       vault's node UUID because a vault can be replicated over many Bluepass
       instances and is therefore not unique.

        <node_uuid>._bluepass._tcp.local

       The TXT records specify a set of properteis, with at least a
       "vault" property containing the UUID of the vault. A "visible" property
       may also be set, indicating whether this node currently accepts pairing
       requests.
    """

    name = 'avahi-zeroconf'

    def __init__(self):
        """Constructor."""
        super(AvahiLocationSource, self).__init__()
        self.connection = GEventDBusConnection(DBUS_BUS_SYSTEM)
        handler = AvahiHandler(self._avahi_event)
        self.connection.add_handler(handler)
        self.logger = logging.getLogger(__name__)
        self.callbacks = []
        self.neighbors = {}
        self.addresses = {}
        self._browser = None
        self._entry_groups = {}

    def _call_avahi(self, path, method, interface, format=None, args=None):
        """INTERNAL: call into Avahi."""
        try:
            reply = self.connection.call_method(path,
                                                method,
                                                interface,
                                                format=format,
                                                args=args,
                                                destination=DBUS_NAME)
        except DBusError as e:
            msg = 'Encounted a D-BUS error for method %s: %s'
            self.logger.error(msg, method, str(e))
            raise LocationError(msg % (method, str(e)))
        return reply

    def _run_callbacks(self, event, *args):
        """Run all registered callbacks."""
        for callback in self.callbacks:
            callback(event, *args)

    def _proto_to_family(self, proto):
        """Convert an Avahi protocol ID to an address family."""
        if proto == PROTO_INET:
            family = socket.AF_INET
        elif proto == PROTO_INET6:
            family = socket.AF_INET6
        else:
            family = -1
        return family

    def _avahi_event(self, event, *args):
        """Single unified callback for AvahiHandler."""
        logger = self.logger
        if event == 'Found':
            node = args[2]
            neighbor = {'node': node, 'source': 'LAN'}
            txt = decode_txt(args[9])
            properties = neighbor['properties'] = {}
            for name, value in txt.items():
                if name in ('nodename', 'vault', 'vaultname'):
                    neighbor[name] = value
                else:
                    properties[name] = value
            for name in ('nodename', 'vault', 'vaultname'):
                if not neighbor.get(name):
                    logger.error('node %s lacks TXT field "%s"', node, name)
                    return
            event = 'NeighborUpdated' if node in self.neighbors \
                        else 'NeighborDiscovered'
            family = self._proto_to_family(args[6])
            if family != socket.AF_INET:
                return
            addr = {
                'family': family,
                'host': args[5],
                'addr': (args[7], args[8])
            }
            addr['id'] = '%s:%s:%s' % (family, args[7], args[8])
            # There can be multiple addresses per node for different
            # interfaces and/or address families. We keep track of this
            # so we distinghuish address changes from new addresses that
            # become available.
            key = '%d:%d' % (args[0], args[1])
            if node not in self.addresses:
                self.addresses[node] = {}
            self.addresses[node][key] = addr
            neighbor['addresses'] = self.addresses[node].values()
            self.neighbors[node] = neighbor
            self._run_callbacks(event, neighbor)
        elif event == 'ItemRemove':
            node = args[2]
            key = '%d:%d' % (args[0], args[1])
            if node not in self.neighbors or key not in self.addresses[node]:
                logger.error('ItemRemove event for unknown node "%s"', node)
                return
            del self.addresses[node][key]
            neighbor = self.neighbors[node]
            neighbor['addresses'] = self.addresses[node].values()
            if not neighbor['addresses']:
                del self.addresses[node]
                del self.neighbors[node]
            event = 'NeighbordUpdated' if node in self.neighbors \
                        else 'NeighborDisappeared'
            self._run_callbacks(event, neighbor)

    def isavailable(self):
        """Return wheter Avahi is available or not."""
        try:
            reply = self._call_avahi(PATH_SERVER, 'GetVersionString',
                                     IFACE_SERVER)
        except LocationError:
            return False
        version = reply.get_args()[0]
        self.logger.info('Found Avahi version %s', version)
        reply = self._call_avahi(PATH_SERVER, 'GetState', IFACE_SERVER)
        state = reply.get_args()[0]
        if state != SERVER_RUNNING:
            self.logger.error('Avahi not in the RUNNING state (instead: %s)',
                              state)
            return False
        return True

    def add_callback(self, callback):
        """Add a callback for this location source. When the first callback is
        added, we start browsing the zeroconf domain."""
        self.callbacks.append(callback)
        if self._browser is not None:
            return
        args = (IFACE_UNSPEC, PROTO_INET, self.service, self.domain, 0)
        reply = self._call_avahi(PATH_SERVER, 'ServiceBrowserNew',
                                 IFACE_SERVER, 'iissu', args)
        self._browser = reply.get_args()[0]

    def register(self,
                 node,
                 nodename,
                 vault,
                 vaultname,
                 address,
                 properties=None):
        """Register a service instance."""
        reply = self._call_avahi(PATH_SERVER, 'EntryGroupNew', IFACE_SERVER)
        group = reply.get_args()[0]
        reply = self._call_avahi(PATH_SERVER, 'GetHostNameFqdn', IFACE_SERVER)
        host = reply.get_args()[0]
        port = address[1]
        properties = properties.copy() if properties else {}
        properties['nodename'] = nodename
        properties['vault'] = vault
        properties['vaultname'] = vaultname
        args = (IFACE_UNSPEC, PROTO_INET, 0, node, self.service, self.domain,
                host, port, encode_txt(properties))
        self._call_avahi(group, 'AddService', IFACE_ENTRY_GROUP, 'iiussssqaay',
                         args)
        self._call_avahi(group, 'Commit', IFACE_ENTRY_GROUP)
        self._entry_groups[node] = (group, properties)

    def set_property(self, node, name, value):
        """Update a property."""
        if node not in self._entry_groups:
            raise RuntimeError('Node is not registered yet')
        group, properties = self._entry_groups[node]
        properties[name] = value
        args = (IFACE_UNSPEC, PROTO_INET, 0, node, self.service, self.domain,
                encode_txt(properties))
        self._call_avahi(group, 'UpdateServiceTxt', IFACE_ENTRY_GROUP,
                         'iiusssaay', args)

    def unregister(self, node):
        """Release our registration."""
        if node not in self._entry_groups:
            raise RuntimeError('Node is not registered yet')
        group, properties = self._entry_groups[node]
        self._call_avahi(group, 'Free', IFACE_ENTRY_GROUP)
        del self._entry_groups[node]
Exemple #8
0
class AvahiLocationSource(ZeroconfLocationSource):
    """Avahi Zeroconf location source.
    
    This location source provides loation services for a local network
    using DNS-SD. This source is for freedesktop like platforms and uses
    Avahi via its D-BUS interface.

    DNS-SD is used in the following way for vault discovery:

    1. The Bluepass service is registered as a PTR record under:

        _bluepass._tcp.local

    2. The previous PTR record will resolve to a list of SRV and TXT records.
       Instead of using the vault UUID as the service name, this uses the
       vault's node UUID because a vault can be replicated over many Bluepass
       instances and is therefore not unique.

        <node_uuid>._bluepass._tcp.local

       The TXT records specify a set of properteis, with at least a
       "vault" property containing the UUID of the vault. A "visible" property
       may also be set, indicating whether this node currently accepts pairing
       requests.
    """

    name = 'avahi-zeroconf'

    def __init__(self):
        """Constructor."""
        super(AvahiLocationSource, self).__init__()
        self.connection = GEventDBusConnection(DBUS_BUS_SYSTEM)
        handler = AvahiHandler(self._avahi_event)
        self.connection.add_handler(handler)
        self.logger = logging.getLogger(__name__)
        self.callbacks = []
        self.neighbors = {}
        self.addresses = {}
        self._browser = None
        self._entry_groups = {}

    def _call_avahi(self, path, method, interface, format=None, args=None):
        """INTERNAL: call into Avahi."""
        try:
            reply = self.connection.call_method(path, method, interface,
                            format=format, args=args, destination=DBUS_NAME)
        except DBusError as e:
            msg = 'Encounted a D-BUS error for method %s: %s'
            self.logger.error(msg, method, str(e))
            raise LocationError(msg % (method, str(e)))
        return reply

    def _run_callbacks(self, event, *args):
        """Run all registered callbacks."""
        for callback in self.callbacks:
            callback(event, *args)

    def _proto_to_family(self, proto):
        """Convert an Avahi protocol ID to an address family."""
        if proto == PROTO_INET:
            family = socket.AF_INET
        elif proto == PROTO_INET6:
            family = socket.AF_INET6
        else:
            family = -1
        return family

    def _avahi_event(self, event, *args):
        """Single unified callback for AvahiHandler."""
        logger = self.logger
        if event == 'Found':
            node = args[2]
            neighbor = { 'node': node, 'source': 'LAN' }
            txt = decode_txt(args[9])
            properties = neighbor['properties'] = {}
            for name,value in txt.items():
                if name in ('nodename', 'vault', 'vaultname'):
                    neighbor[name] = value
                else:
                    properties[name] = value
            for name in ('nodename', 'vault', 'vaultname'):
                if not neighbor.get(name):
                    logger.error('node %s lacks TXT field "%s"', node, name)
                    return
            event = 'NeighborUpdated' if node in self.neighbors \
                        else 'NeighborDiscovered'
            family = self._proto_to_family(args[6])
            if family != socket.AF_INET:
                return
            addr = { 'family': family, 'host': args[5],
                     'addr': (args[7], args[8]) }
            addr['id'] = '%s:%s:%s' % (family, args[7], args[8])
            # There can be multiple addresses per node for different
            # interfaces and/or address families. We keep track of this
            # so we distinghuish address changes from new addresses that
            # become available.
            key = '%d:%d' % (args[0], args[1])
            if node not in self.addresses:
                self.addresses[node] = {}
            self.addresses[node][key] = addr
            neighbor['addresses'] = self.addresses[node].values()
            self.neighbors[node] = neighbor
            self._run_callbacks(event, neighbor)
        elif event == 'ItemRemove':
            node = args[2]
            key = '%d:%d' % (args[0], args[1])
            if node not in self.neighbors or key not in self.addresses[node]:
                logger.error('ItemRemove event for unknown node "%s"', node)
                return
            del self.addresses[node][key]
            neighbor = self.neighbors[node]
            neighbor['addresses'] = self.addresses[node].values()
            if not neighbor['addresses']:
                del self.addresses[node]
                del self.neighbors[node]
            event = 'NeighbordUpdated' if node in self.neighbors \
                        else 'NeighborDisappeared'
            self._run_callbacks(event, neighbor)

    def isavailable(self):
        """Return wheter Avahi is available or not."""
        try:
            reply = self._call_avahi(PATH_SERVER, 'GetVersionString', IFACE_SERVER)
        except LocationError:
            return False
        version = reply.get_args()[0]
        self.logger.info('Found Avahi version %s', version)
        reply = self._call_avahi(PATH_SERVER, 'GetState', IFACE_SERVER)
        state = reply.get_args()[0]
        if state != SERVER_RUNNING:
            self.logger.error('Avahi not in the RUNNING state (instead: %s)', state)
            return False
        return True

    def add_callback(self, callback):
        """Add a callback for this location source. When the first callback is
        added, we start browsing the zeroconf domain."""
        self.callbacks.append(callback)
        if self._browser is not None:
            return
        args = (IFACE_UNSPEC, PROTO_INET, self.service, self.domain, 0)
        reply = self._call_avahi(PATH_SERVER, 'ServiceBrowserNew',
                                 IFACE_SERVER, 'iissu', args)
        self._browser = reply.get_args()[0]

    def register(self, node, nodename, vault, vaultname, address,
                 properties=None):
        """Register a service instance."""
        reply = self._call_avahi(PATH_SERVER, 'EntryGroupNew', IFACE_SERVER)
        group = reply.get_args()[0]
        reply = self._call_avahi(PATH_SERVER, 'GetHostNameFqdn', IFACE_SERVER)
        host = reply.get_args()[0]
        port = address[1]
        properties = properties.copy() if properties else {}
        properties['nodename'] = nodename
        properties['vault'] = vault
        properties['vaultname'] = vaultname
        args = (IFACE_UNSPEC, PROTO_INET, 0, node, self.service,
                self.domain, host, port, encode_txt(properties))
        self._call_avahi(group, 'AddService', IFACE_ENTRY_GROUP,
                         'iiussssqaay', args)
        self._call_avahi(group, 'Commit', IFACE_ENTRY_GROUP)
        self._entry_groups[node] = (group, properties)

    def set_property(self, node, name, value):
        """Update a property."""
        if node not in self._entry_groups:
            raise RuntimeError('Node is not registered yet')
        group, properties = self._entry_groups[node]
        properties[name] = value
        args = (IFACE_UNSPEC, PROTO_INET, 0, node, self.service,
                self.domain, encode_txt(properties))
        self._call_avahi(group, 'UpdateServiceTxt', IFACE_ENTRY_GROUP,
                         'iiusssaay', args)

    def unregister(self, node):
        """Release our registration."""
        if node not in self._entry_groups:
            raise RuntimeError('Node is not registered yet')
        group, properties = self._entry_groups[node]
        self._call_avahi(group, 'Free', IFACE_ENTRY_GROUP)
        del self._entry_groups[node]
        <interface name="com.mastervolt.Iris">
                <method name="HelloMethod">
                        <arg type="s" name="somestring" direction="in" />
                        <arg type="i" name="someint" direction="in" />
                        <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
                </method>
                 <signal name="Hello">
                        <arg type="as" name="arrayofstrings" direction="in" />
                </signal>
                <!-- Add more methods/signals if you want -->
        </interface>
</node>"""
        
        self.set_response("s", [xml])

conn = GEventDBusConnection(DBUS_BUS_SESSION)
handler = GEventHandler()
conn.add_handler(handler)

print 'Listening for signals, with gevent dispatcher.'
print 'In another terminal, issue:'
print
print '  $ dbus-send --session --type=signal --dest={} /com/example/TDBus com.example.Hello.Hello'.format(conn.get_unique_name())
print '  $ dbus-send --session --print-reply --type=method_call --dest={} /com/example/TDBus com.example.Hello.HelloMethod'.format(conn.get_unique_name())
print
print 'Press CTRL-c to exit.'
print


from gevent.hub import get_hub
try:
Exemple #10
0
 def test_get_unique_name(self):
     conn = GEventDBusConnection(DBUS_BUS_SESSION)
     name = conn.get_unique_name()
     assert name.startswith(':')
     conn.close()
Exemple #11
0
 def test_connection_multiple_open(self):
     conn = GEventDBusConnection(DBUS_BUS_SESSION)
     conn.close()
     conn.open(DBUS_BUS_SESSION)
     conn.close()
Exemple #12
0
 def test_connection_init(self):
     conn = GEventDBusConnection(DBUS_BUS_SESSION)
     conn.close()