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