def test_get_unique_name(self): # Ensure that get_unique_name() works client = DbusClient() client.connect('session') unique_name = client.get_unique_name() self.assertIsInstance(unique_name, six.text_type) self.assertTrue(unique_name.startswith(':')) client.close()
def __init__(self): """Constructor.""" super(AvahiLocationSource, self).__init__() handler = AvahiHandler(self._avahi_event) self.client = DbusClient(handler) self.client.connect('system') self.logger = logging.getLogger(__name__) self.callbacks = [] self.neighbors = {} self.addresses = {} self._browser = None self._entry_groups = {}
def test_auth_tcp(self): # Test that authentication works over TCP server = DbusServer(echo_app) addr = 'tcp:host=127.0.0.1,port=0' server.listen(addr) client = DbusClient() client.connect(server.addresses[0]) cproto = client.connection[1] cauth = cproto._authenticator sproto = list(server.connections)[0][1] sauth = sproto._authenticator self.assertTrue(cauth.authenticationSucceeded()) self.assertTrue(sauth.authenticationSucceeded()) self.assertIsInstance(cproto.server_guid, six.text_type) self.assertTrue(cproto.server_guid.isalnum()) self.assertEqual(cproto.server_guid, cauth.getGUID()) self.assertEqual(cproto.server_guid, sproto.server_guid) self.assertEqual(sproto.server_guid, sauth.getGUID()) self.assertEqual(cauth.getMechanismName(), sauth.getMechanismName()) if hasattr(os, 'fork'): self.assertEqual(cauth.getMechanismName(), 'DBUS_COOKIE_SHA1') else: self.assertEqual(cauth.getMechanismName(), 'ANONYMOUS') client.close() server.close()
def test_auth_pipe(self): # Test that authentication works over a Pipe. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) cproto = client.connection[1] cauth = cproto._authenticator sproto = list(server.connections)[0][1] sauth = sproto._authenticator self.assertTrue(cauth.authenticationSucceeded()) self.assertTrue(sauth.authenticationSucceeded()) self.assertIsInstance(cproto.server_guid, six.text_type) self.assertTrue(cproto.server_guid.isalnum()) self.assertEqual(cproto.server_guid, cauth.getGUID()) self.assertEqual(cproto.server_guid, sproto.server_guid) self.assertEqual(sproto.server_guid, sauth.getGUID()) self.assertEqual(cauth.getMechanismName(), sauth.getMechanismName()) if hasattr(socket, 'SO_PEERCRED'): self.assertEqual(cauth.getMechanismName(), 'EXTERNAL') elif hasattr(os, 'fork'): self.assertEqual(cauth.getMechanismName(), 'DBUS_COOKIE_SHA1') else: self.assertEqual(cauth.getMechanismName(), 'ANONYMOUS') client.close() server.close()
def test_call_method(self): # Ensure that calling a method over a Unix socket works. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) result = client.call_method('bus.name', '/path', 'my.iface', 'Echo') self.assertEqual(result, ()) server.close() client.close()
def test_call_method_tcp(self): # Ensure that calling a method over TCP works. server = DbusServer(echo_app) addr = 'tcp:host=127.0.0.1,port=0' server.listen(addr) client = DbusClient() client.connect(server.addresses[0]) result = client.call_method('bus.name', '/path', 'my.iface', 'Echo') self.assertEqual(result, ()) server.close() client.close()
def test_call_method_str_args(self): # Ensure that calling a method with string arguments works. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) result = client.call_method('bus.name', '/path', 'my.iface', 'Echo', signature='s', args=['foo']) self.assertEqual(result, ('foo',)) result = client.call_method('bus.name', '/path', 'my.iface', 'Echo', signature='ss', args=['foo', 'bar']) self.assertEqual(result, ('foo', 'bar')) server.close() client.close()
def test_call_method_int_args(self): # Ensure that calling a method with integer arguments works. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) result = client.call_method('bus.name', '/path', 'my.iface', 'Echo', signature='i', args=[1]) self.assertEqual(result, (1,)) result = client.call_method('bus.name', '/path', 'my.iface', 'Echo', signature='ii', args=[1, 2]) self.assertEqual(result, (1, 2)) server.close() client.close()
def perf_message_throughput_tcp(self): # Test roundtrips of a simple method call over TCP. server = DbusServer(echo_app) addr = 'tcp:host=127.0.0.1,port=0' server.listen(addr) client = DbusClient() client.connect(server.addresses[0]) nmessages = 0 t0 = t1 = time.time() while t1 - t0 < 0.2: client.call_method('bus.name', '/path', 'my.iface', 'Echo') t1 = time.time() nmessages += 1 throughput = nmessages / (t1 - t0) self.add_result(throughput) server.close() client.close()
def perf_message_throughput_pipe(self): # Test roundtrips of a simple method call over a Pipe server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) nmessages = 0 t0 = t1 = time.time() while t1 - t0 < 0.2: client.call_method('bus.name', '/path', 'my.iface', 'Echo') t1 = time.time() nmessages += 1 throughput = nmessages / (t1 - t0) self.add_result(throughput) server.close() client.close()
def test_get_unique_name(self): # Ensure that get_unique_name() works client and server side server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) unique_name = client.get_unique_name() self.assertIsInstance(unique_name, six.text_type) self.assertTrue(unique_name.startswith(':')) sproto = list(server.connections)[0][1] self.assertEqual(unique_name, sproto.get_unique_name()) server.close() client.close()
def test_call_listnames(self): # Call the ListNames() bus method and ensure the results are a list of # strings. client = DbusClient() client.connect('session') result = client.call_method('org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'ListNames') self.assertIsInstance(result, tuple) self.assertEqual(len(result), 1) names = result[0] self.assertIsInstance(names, list) self.assertGreater(len(names), 0) for name in names: self.assertIsInstance(name, six.text_type) client.close()
def test_call_method_error(self): # Ensure that a method can return an error and that in this case a # DbusMethodCallError is raised. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) exc = self.assertRaises(DbusMethodCallError, client.call_method, 'bus.name', '/path', 'my.iface', 'Error') self.assertEqual(exc.error, 'Echo.Error') self.assertEqual(exc.args, ()) server.close() client.close()
def test_call_method_error_args(self): # Call a method that will return an error with arguments. The arguments # should be available from the exception. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) exc = self.assertRaises(DbusMethodCallError, client.call_method, 'bus.name', '/path', 'my.iface', 'Error', signature='ss', args=('foo', 'bar')) self.assertEqual(exc.error, 'Echo.Error') self.assertEqual(exc.args, ('foo', 'bar')) server.close() client.close()
def test_send_garbage(self): # Send random garbage and ensure the connection gets dropped. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) client = DbusClient() client.connect(addr) exc = None try: while True: chunk = os.urandom(100) client.transport.write(chunk) gruvi.sleep(0) except Exception as e: exc = e self.assertIsInstance(exc, TransportError) server.close() client.close()
def test_connection_limit(self): # Establish more connections than the DBUS server is willing to accept. # The connections should be closed. server = DbusServer(echo_app) addr = 'unix:path=' + self.pipename() server.listen(addr) server.max_connections = 10 clients = [] exc = None try: for i in range(15): client = DbusClient() client.connect(addr) clients.append(client) except Exception as e: exc = e self.assertIsInstance(exc, TransportError) self.assertLessEqual(len(server.connections), server.max_connections) for client in clients: client.close() server.close()
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__() handler = AvahiHandler(self._avahi_event) self.client = DbusClient(handler) self.client.connect('system') self.logger = logging.getLogger(__name__) self.callbacks = [] self.neighbors = {} self.addresses = {} self._browser = None self._entry_groups = {} def _call_avahi(self, path, method, interface, signature=None, args=None): """INTERNAL: call into Avahi.""" try: reply = self.client.call_method(DBUS_NAME, path, interface, method, signature=signature, args=args) 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[0] if len(reply) == 1 else 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'] = list(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'] = list(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: version = self._call_avahi(PATH_SERVER, 'GetVersionString', IFACE_SERVER) except LocationError: return False self.logger.info('Found Avahi version %s', version) state = self._call_avahi(PATH_SERVER, 'GetState', IFACE_SERVER) 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) self._browser = self._call_avahi(PATH_SERVER, 'ServiceBrowserNew', IFACE_SERVER, 'iissu', args) def register(self, node, nodename, vault, vaultname, address, properties=None): """Register a service instance.""" group = self._call_avahi(PATH_SERVER, 'EntryGroupNew', IFACE_SERVER) host = self._call_avahi(PATH_SERVER, 'GetHostNameFqdn', IFACE_SERVER) 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]