1#!/usr/bin/python
2
3import socket
4
5import dbus
6import dbus.service
7from dbus.lowlevel import SignalMessage
8import gobject
9import glib
10
11from dbus.mainloop.glib import DBusGMainLoop
12DBusGMainLoop(set_as_default=True)
13
14from avahitest import check_ipv6_enabled
15
16AVAHI_NAME = 'org.freedesktop.Avahi'
17AVAHI_IFACE_SERVER = 'org.freedesktop.Avahi.Server'
18AVAHI_IFACE_ENTRY_GROUP = 'org.freedesktop.Avahi.EntryGroup'
19AVAHI_IFACE_SERVICE_BROWSER = 'org.freedesktop.Avahi.ServiceBrowser'
20AVAHI_IFACE_SERVICE_RESOLVER = 'org.freedesktop.Avahi.ServiceResolver'
21
22AVAHI_DNS_CLASS_IN = 1
23AVAHI_DNS_TYPE_A = 1
24
25AVAHI_PROTO_INET = 0
26AVAHI_PROTO_INET6 = 1
27AVAHI_PROTO_UNSPEC = -1
28
29AVAHI_SERVER_INVALID = 0
30AVAHI_SERVER_REGISTERING = 1
31AVAHI_SERVER_RUNNING = 2
32AVAHI_SERVER_COLLISION = 3
33AVAHI_SERVER_FAILURE = 4
34
35DOMAIN = 'local'
36
37def emit_signal(object_path, interface, name, destination, signature, *args):
38    message = SignalMessage(object_path, interface, name)
39    message.append(*args, signature=signature)
40
41    if destination is not None:
42        message.set_destination(destination)
43
44    dbus.SystemBus().send_message(message)
45
46class Model(object):
47    def __init__(self):
48        self._service_browsers = []
49        self._service_browser_index = 1
50        self._service_resolvers = []
51        self._service_resolver_index = 1
52        self._entries = []
53        self._address_records = {}
54
55    def new_service_browser(self, type_, client):
56        service_browser = ServiceBrowser(client, self._service_browser_index, type_)
57        self._service_browser_index += 1
58        self._service_browsers.append(service_browser)
59
60        glib.idle_add(self.__browse_idle_cb, service_browser)
61
62        return service_browser.object_path
63
64    def __browse_idle_cb(self, service_browser):
65        for entry in self._entries:
66            if entry.type == service_browser.type:
67                self._emit_new_item(service_browser, entry)
68
69    def _find_entry(self, type_, name):
70        for entry in self._entries:
71            if entry.type == type_ and entry.name == name:
72                return entry
73        return None
74
75    def new_service_resolver(self, type_, name, protocol, client):
76        entry = self._find_entry(type_, name)
77        service_resolver = ServiceResolver(self._service_resolver_index,
78                                           client, type_, name, protocol)
79        self._service_resolver_index += 1
80        self._service_resolvers.append(service_resolver)
81
82        glib.idle_add(self.__entry_found_idle_cb, service_resolver, entry)
83
84        return service_resolver.object_path
85
86    def __entry_found_idle_cb(self, service_resolver, entry):
87        if entry is None:
88            emit_signal(service_resolver.object_path,
89                        AVAHI_IFACE_SERVICE_RESOLVER, 'Failure',
90                        service_resolver.client, 's',
91                        'no entry could be found')
92        else:
93            self._emit_found(service_resolver, entry)
94
95    def _resolve_hostname(self, protocol, hostname):
96        if hostname in self._address_records:
97            return self._address_records[hostname]
98        else:
99            if protocol == AVAHI_PROTO_INET6:
100                return '::1'
101            else:
102                return '127.0.0.1'
103
104    def update_entry(self, interface, protocol, flags, name, type_, domain,
105                     host, port, txt):
106        entry = self._find_entry(type_, name)
107
108        if interface == -1:
109            interface = 0
110
111        if host is None:
112            host = entry.host
113
114        if port is None:
115            port = entry.port
116
117        if entry is None:
118            entry = Entry(interface, protocol, flags, name, type_, domain,
119                          host, port, txt)
120            self._entries.append(entry)
121        else:
122            entry.update(interface, protocol, flags, domain, host, port, txt)
123
124        for service_browser in self._service_browsers:
125            if service_browser.type == type_:
126                self._emit_new_item(service_browser, entry)
127
128        for service_resolver in self._service_resolvers:
129            if service_resolver.type == type_ and \
130                service_resolver.name == name:
131                self._emit_found(service_resolver, entry)
132
133    def add_record(self, interface, protocol, flags, name, clazz, type_, ttl,
134                   rdata):
135        if clazz == AVAHI_DNS_CLASS_IN and type_ == AVAHI_DNS_TYPE_A:
136            self._address_records[name] = socket.inet_ntoa(rdata)
137
138    def remove_entry(self, type_, name):
139        entry = self._find_entry(type_, name)
140        if entry is None:
141            # Entry may have been created by more than one EntryGroup
142            return
143
144        for service_browser in self._service_browsers:
145            if service_browser.type == type_:
146                self._emit_item_remove(service_browser, entry)
147
148        self._entries.remove(entry)
149
150    def _emit_new_item(self, service_browser, entry):
151        if entry.protocol == AVAHI_PROTO_UNSPEC:
152            protocols = (AVAHI_PROTO_INET, AVAHI_PROTO_INET6)
153        else:
154            protocols = (entry.protocol,)
155
156        for protocol in protocols:
157            emit_signal(service_browser.object_path,
158                        AVAHI_IFACE_SERVICE_BROWSER, 'ItemNew',
159                        service_browser.client, 'iisssu',
160                        entry.interface, protocol, entry.name, entry.type,
161                        entry.domain, entry.flags)
162
163    def _emit_item_remove(self, service_browser, entry):
164        if entry.protocol == AVAHI_PROTO_UNSPEC:
165            protocols = (AVAHI_PROTO_INET, AVAHI_PROTO_INET6)
166        else:
167            protocols = (entry.protocol,)
168
169        for protocol in protocols:
170            emit_signal(service_browser.object_path,
171                        AVAHI_IFACE_SERVICE_BROWSER, 'ItemRemove',
172                        service_browser.client, 'iisssu',
173                        entry.interface, protocol, entry.name, entry.type,
174                        entry.domain, entry.flags)
175
176    def _emit_found(self, service_resolver, entry):
177        protocols = []
178        if service_resolver.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET]:
179            if entry.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET]:
180                protocols.append(AVAHI_PROTO_INET)
181
182        if check_ipv6_enabled() and \
183                service_resolver.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET6]:
184            if entry.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET6]:
185                protocols.append(AVAHI_PROTO_INET6)
186
187        for protocol in protocols:
188            address = self._resolve_hostname(protocol, entry.host)
189            emit_signal(service_resolver.object_path,
190                        AVAHI_IFACE_SERVICE_RESOLVER, 'Found',
191                        service_resolver.client, 'iissssisqaayu',
192                        entry.interface, protocol, entry.name, entry.type,
193                        entry.domain, entry.host, entry.aprotocol,
194                        address, entry.port, entry.txt, entry.flags)
195
196    def remove_client(self, client):
197        for service_browser in self._service_browsers[:]:
198            if service_browser.client == client:
199                service_browser.Free()
200                service_browser.remove_from_connection()
201                self._service_browsers.remove(service_browser)
202
203        for service_resolver in self._service_resolvers[:]:
204            if service_resolver.client == client:
205                service_resolver.Free()
206                service_resolver.remove_from_connection()
207                self._service_resolvers.remove(service_resolver)
208
209
210class Entry(object):
211    def __init__(self, interface, protocol, flags, name, type_, domain, host,
212                 port, txt):
213        self.name = name
214        self.type = type_
215
216        self.interface = None
217        self.protocol = None
218        self.aprotocol = None
219        self.flags = None
220        self.domain = None
221        self.host = None
222        self.port = None
223        self.txt = None
224
225        self.update(interface, protocol, flags, domain, host, port, txt)
226
227    def update(self, interface, protocol, flags, domain, host, port, txt):
228        self.interface = interface
229        self.protocol = protocol
230        self.aprotocol = protocol
231        self.flags = flags
232        self.domain = domain
233        self.host = host
234        self.port = port
235        self.txt = txt
236
237class Avahi(dbus.service.Object):
238    def __init__(self):
239        bus = dbus.SystemBus()
240        name = dbus.service.BusName(AVAHI_NAME, bus)
241        dbus.service.Object.__init__(self, conn=bus, object_path='/',
242                                     bus_name=name)
243
244        bus.add_signal_receiver(self.__name_owner_changed_cb,
245                                signal_name='NameOwnerChanged',
246                                dbus_interface='org.freedesktop.DBus')
247
248        self._entry_groups = []
249        self._entry_group_index = 1
250        self._model = Model()
251
252    def __name_owner_changed_cb(self, name, old_owner, new_owner):
253        if new_owner == '':
254            for entry_group in self._entry_groups[:]:
255                if entry_group.client == name:
256                    entry_group.Free()
257                    entry_group.remove_from_connection()
258                    self._entry_groups.remove(entry_group)
259
260    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
261                         in_signature='', out_signature='u')
262    def GetAPIVersion(self):
263        return 515
264
265    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
266                         in_signature='', out_signature='s')
267    def GetHostName(self):
268        return 'testsuite'
269
270    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
271                         in_signature='', out_signature='s')
272    def GetHostNameFqdn(self):
273        return self.GetHostName() + '.' + self.GetDomainName()
274
275    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
276                         in_signature='', out_signature='s')
277    def GetDomainName(self):
278        return DOMAIN
279
280    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
281                         in_signature='', out_signature='i')
282    def GetState(self):
283        return AVAHI_SERVER_RUNNING
284
285    @dbus.service.signal(dbus_interface=AVAHI_IFACE_SERVER, signature='is')
286    def StateChanged(self, state, error):
287        pass
288
289    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
290                         in_signature='', out_signature='o',
291                         sender_keyword='sender')
292    def EntryGroupNew(self, sender):
293        entry_group = EntryGroup(sender, self._entry_group_index, self._model)
294        self._entry_group_index += 1
295        self._entry_groups.append(entry_group)
296        return entry_group.object_path
297
298    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
299                         in_signature='iissu', out_signature='o',
300                         sender_keyword='sender')
301    def ServiceBrowserNew(self, interface, protocol, type_, domain, flags, sender):
302        return self._model.new_service_browser(type_, sender)
303
304    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
305                         in_signature='iisssiu', out_signature='o',
306                         sender_keyword='sender')
307    def ServiceResolverNew(self, interface, protocol, name, type_, domain, aprotocol, flags, sender):
308        return self._model.new_service_resolver(type_, name, protocol, sender)
309
310
311class EntryGroup(dbus.service.Object):
312    def __init__(self, client, index, model):
313        bus = dbus.SystemBus()
314        self.object_path = '/Client%u/EntryGroup%u' % (1, index)
315        dbus.service.Object.__init__(self, conn=bus,
316                                     object_path=self.object_path)
317
318        self._state = 0
319        self.client = client
320        self._model = model
321
322        self._entries = []
323
324    def get_service(self, name):
325        return self._services.get(name, None)
326
327    @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
328                         in_signature='iiussssqaay', out_signature='',
329                         byte_arrays=True)
330    def AddService(self, interface, protocol, flags, name, type_, domain, host,
331                   port, txt):
332        if not host:
333            host = socket.gethostname()
334
335        if not domain:
336            domain = DOMAIN
337
338        self._model.update_entry(interface, protocol, flags, name, type_, domain,
339                                 host, port, txt)
340        self._entries.append((type_, name))
341
342    @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
343                         in_signature='iiusssaay', out_signature='',
344                         byte_arrays=True)
345    def UpdateServiceTxt(self, interface, protocol, flags, name, type_, domain, txt):
346        self._model.update_entry(interface, protocol, flags, name, type_, domain,
347                                 None, None, txt)
348
349    @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
350                         in_signature='', out_signature='')
351    def Commit(self):
352        self._set_state(AVAHI_SERVER_REGISTERING)
353        glib.idle_add(lambda: self._set_state(AVAHI_SERVER_RUNNING))
354
355    def _set_state(self, new_state):
356        self._state = new_state
357
358        message = SignalMessage(self.object_path,
359                                AVAHI_IFACE_ENTRY_GROUP,
360                                'StateChanged')
361        message.append(self._state, 'org.freedesktop.Avahi.Success',
362                       signature='is')
363        message.set_destination(self.client)
364
365        dbus.SystemBus().send_message(message)
366
367    @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
368                         in_signature='', out_signature='i')
369    def GetState(self):
370        return self._state
371
372    @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
373                         in_signature='iiusqquay', out_signature='',
374                         byte_arrays=True)
375    def AddRecord(self, interface, protocol, flags, name, clazz, type_, ttl,
376                  rdata):
377        self._model.add_record(interface, protocol, flags, name, clazz, type_,
378                               ttl, rdata)
379
380    @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
381                         in_signature='', out_signature='')
382    def Free(self):
383        for type_, name in self._entries[:]:
384            self._model.remove_entry(type_, name)
385            self._entries.remove((type_, name))
386
387
388class ServiceBrowser(dbus.service.Object):
389    def __init__(self, client, index, type_):
390        bus = dbus.SystemBus()
391        self.object_path = '/Client%u/ServiceBrowser%u' % (1, index)
392        dbus.service.Object.__init__(self, conn=bus,
393                                     object_path=self.object_path)
394
395        self.client = client
396        self.type = type_
397
398        def send_all_for_now():
399            emit_signal(self.object_path,
400                        AVAHI_IFACE_SERVICE_BROWSER, 'AllForNow',
401                        client, '')
402        gobject.idle_add(send_all_for_now)
403
404    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVICE_BROWSER,
405                         in_signature='', out_signature='')
406    def Free(self):
407        pass
408
409
410class ServiceResolver(dbus.service.Object):
411    def __init__(self, index, client, type_, name, protocol):
412        bus = dbus.SystemBus()
413        self.object_path = '/Client%u/ServiceResolver%u' % (1, index)
414        dbus.service.Object.__init__(self, conn=bus,
415                                     object_path=self.object_path)
416        self.client = client
417        self.type = type_
418        self.name = name
419        self.protocol = protocol
420
421    @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVICE_RESOLVER,
422                         in_signature='', out_signature='')
423    def Free(self):
424        pass
425
426
427avahi = Avahi()
428
429loop = gobject.MainLoop()
430loop.run()
431