Rework PS owner service handling to use avahi flags for local services

This commit is contained in:
Dan Williams 2006-09-12 12:36:24 -04:00
parent 4ae80802ce
commit f205e8c67b
3 changed files with 83 additions and 95 deletions

View File

@ -287,9 +287,9 @@ class Buddy(object):
class Owner(Buddy): class Owner(Buddy):
"""Class representing the owner of the machine. This is the client """Class representing the owner of the machine. This is the client
portion of the Owner, paired with the server portion in Owner.py.""" portion of the Owner, paired with the server portion in Owner.py."""
def __init__(self, ps, bus_name, object_id, nick): def __init__(self, ps, bus_name, object_id):
Buddy.__init__(self, bus_name, object_id, None) Buddy.__init__(self, bus_name, object_id, None)
self._nick_name = nick self._nick_name = env.get_nick_name()
self._color = env.get_color() self._color = env.get_color()
self._ps = ps self._ps = ps
@ -303,9 +303,12 @@ class Owner(Buddy):
# service added to the Owner determines the owner's address # service added to the Owner determines the owner's address
source_addr = service.get_source_address() source_addr = service.get_source_address()
if self._address is None: if self._address is None:
if self._ps.is_local_ip_address(source_addr): if service.is_local():
self._address = source_addr self._address = source_addr
self._dbus_helper.PropertyChanged(['ip4_address']) self._dbus_helper.PropertyChanged(['ip4_address'])
logging.debug("Adding owner service %s.%s at %s:%d." % (service.get_name(),
service.get_type(), service.get_source_address(),
service.get_port()))
return Buddy.add_service(self, service) return Buddy.add_service(self, service)
def is_owner(self): def is_owner(self):

View File

@ -4,32 +4,15 @@ import Service
import Activity import Activity
import random import random
import logging import logging
from sugar import env
from sugar import util from sugar import util
def _get_local_ip_address(ifname): _SA_UNRESOLVED = 0
"""Call Linux specific bits to retrieve our own IP address.""" _SA_RESOLVE_PENDING = 1
import socket _SA_RESOLVED = 2
import sys
import fcntl
addr = None
SIOCGIFADDR = 0x8915
sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
ifreq = (ifname + '\0'*32)[:32]
result = fcntl.ioctl(sockfd.fileno(), SIOCGIFADDR, ifreq)
addr = socket.inet_ntoa(result[20:24])
except IOError, exc:
print "Error getting IP address: %s" % exc
sockfd.close()
return addr
class ServiceAdv(object): class ServiceAdv(object):
"""Wrapper class to track services from Avahi.""" """Wrapper class to track services from Avahi."""
def __init__(self, interface, protocol, name, stype, domain): def __init__(self, interface, protocol, name, stype, domain, local):
self._interface = interface self._interface = interface
self._protocol = protocol self._protocol = protocol
if type(name) != type(u""): if type(name) != type(u""):
@ -42,7 +25,10 @@ class ServiceAdv(object):
raise ValueError("service advertisement domain must be unicode.") raise ValueError("service advertisement domain must be unicode.")
self._domain = domain self._domain = domain
self._service = None self._service = None
self._resolved = False if type(local) != type(False):
raise ValueError("local must be a boolean.")
self._local = local
self._state = _SA_UNRESOLVED
def interface(self): def interface(self):
return self._interface return self._interface
@ -54,16 +40,21 @@ class ServiceAdv(object):
return self._stype return self._stype
def domain(self): def domain(self):
return self._domain return self._domain
def is_local(self):
return self._local
def service(self): def service(self):
return self._service return self._service
def set_service(self, service): def set_service(self, service):
if not isinstance(service, Service.Service): if not isinstance(service, Service.Service):
raise ValueError("must be a valid service.") raise ValueError("must be a valid service.")
self._service = service self._service = service
def resolved(self): def state(self):
return self._resolved return self._state
def set_resolved(self, resolved): def set_state(self, state):
self._resolved = resolved if state == _SA_RESOLVE_PENDING:
if self._state == _SA_RESOLVED:
raise ValueError("Can't reset to resolve pending from resolved.")
self._state = state
_PRESENCE_SERVICE = "org.laptop.Presence" _PRESENCE_SERVICE = "org.laptop.Presence"
@ -246,10 +237,9 @@ class PresenceService(object):
self._services = {} # (name, type) -> Service self._services = {} # (name, type) -> Service
self._activities = {} # activity id -> Activity self._activities = {} # activity id -> Activity
# Keep track of stuff we're already browsing with ZC # Keep track of stuff we're already browsing
self._service_type_browsers = {} self._service_type_browsers = {}
self._service_browsers = {} self._service_browsers = {}
self._resolve_queue = [] # Track resolve requests
# Resolved service list # Resolved service list
self._service_advs = [] self._service_advs = []
@ -263,10 +253,9 @@ class PresenceService(object):
self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name) self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name)
# Our owner object # Our owner object
owner_nick = env.get_nick_name()
objid = self._get_next_object_id() objid = self._get_next_object_id()
self._owner = Buddy.Owner(self, self._bus_name, objid, owner_nick) self._owner = Buddy.Owner(self, self._bus_name, objid)
self._buddies[owner_nick] = self._owner self._buddies[self._owner.get_name()] = self._owner
self._started = False self._started = False
@ -341,13 +330,10 @@ class PresenceService(object):
def get_owner(self): def get_owner(self):
return self._owner return self._owner
def is_local_ip_address(self, address): def _find_service_adv(self, interface=None, protocol=None, name=None,
if address in self._local_addrs.values(): stype=None, domain=None, local=None):
return True """Search a list of service advertisements for ones matching
return False certain criteria."""
def _find_service_adv(self, interface=None, protocol=None, name=None, stype=None, domain=None):
"""Search a list of service advertisements for ones matching certain criteria."""
adv_list = [] adv_list = []
for adv in self._service_advs: for adv in self._service_advs:
if interface and adv.interface() != interface: if interface and adv.interface() != interface:
@ -360,10 +346,12 @@ class PresenceService(object):
continue continue
if domain and adv.domain() != domain: if domain and adv.domain() != domain:
continue continue
if local is not None and adv.is_local() != local:
continue
adv_list.append(adv) adv_list.append(adv)
return adv_list return adv_list
def _handle_new_service_for_buddy(self, service): def _handle_new_service_for_buddy(self, service, local):
"""Deal with a new discovered service object.""" """Deal with a new discovered service object."""
# Once a service is resolved, we match it up to an existing buddy, # Once a service is resolved, we match it up to an existing buddy,
# or create a new Buddy if this is the first service known about the buddy # or create a new Buddy if this is the first service known about the buddy
@ -420,21 +408,17 @@ class PresenceService(object):
self._dbus_helper.ActivityDisappeared(activity.object_path()) self._dbus_helper.ActivityDisappeared(activity.object_path())
del self._activities[actid] del self._activities[actid]
def _resolve_service_reply_cb(self, interface, protocol, full_name, stype, domain, host, aprotocol, address, port, txt, flags): def _resolve_service_reply_cb(self, adv, interface, protocol, full_name,
"""When the service discovery finally gets here, we've got enough information about the stype, domain, host, aprotocol, address, port, txt, flags):
service to assign it to a buddy.""" """When the service discovery finally gets here, we've got enough
logging.debug("resolved service '%s' type '%s' domain '%s' to %s:%s" % (full_name, stype, domain, address, port)) information about the service to assign it to a buddy."""
logging.debug("Resolved service '%s' type '%s' domain '%s' to " \
" %s:%s" % (full_name, stype, domain, address, port))
# If this service was previously unresolved, remove it from the if not adv in self._service_advs:
# unresolved list return False
adv_list = self._find_service_adv(interface=interface, protocol=protocol, if adv.state() != _SA_RESOLVED:
name=full_name, stype=stype, domain=domain)
if not adv_list:
return False return False
adv = adv_list[0]
adv.set_resolved(True)
if adv in self._resolve_queue:
self._resolve_queue.remove(adv)
# See if we know about this service already # See if we know about this service already
key = (full_name, stype) key = (full_name, stype)
@ -442,12 +426,12 @@ class PresenceService(object):
objid = self._get_next_object_id() objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=full_name, service = Service.Service(self._bus_name, objid, name=full_name,
stype=stype, domain=domain, address=address, port=port, stype=stype, domain=domain, address=address, port=port,
properties=txt, source_address=address) properties=txt, source_address=address, local=adv.is_local())
self._services[key] = service self._services[key] = service
else: else:
# Already tracking this service; likely we were the one that shared it # Already tracking this service; likely we were the one that shared it
# in the first place, and therefore the source address would have been # in the first place, and therefore the source address would not have
# set yet # been set yet
service = self._services[key] service = self._services[key]
if not service.get_source_address(): if not service.get_source_address():
service.set_source_address(address) service.set_source_address(address)
@ -456,51 +440,44 @@ class PresenceService(object):
adv.set_service(service) adv.set_service(service)
# Merge the service into our buddy and activity lists, if needed # Merge the service into our buddy and activity lists, if needed
buddy = self._handle_new_service_for_buddy(service) buddy = self._handle_new_service_for_buddy(service, adv.is_local())
if buddy and service.get_activity_id(): if buddy and service.get_activity_id():
self._handle_new_activity_service(service) self._handle_new_activity_service(service)
return False return False
def _resolve_service_reply_cb_glue(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): def _resolve_service_reply_cb_glue(self, adv, interface, protocol, name,
gobject.idle_add(self._resolve_service_reply_cb, interface, protocol, stype, domain, host, aprotocol, address, port, txt, flags):
name, stype, domain, host, aprotocol, address, port, txt, flags) adv.set_state(_SA_RESOLVED)
gobject.idle_add(self._resolve_service_reply_cb, adv, interface,
protocol, name, stype, domain, host, aprotocol, address,
port, txt, flags)
def _resolve_service_error_handler(self, err): def _resolve_service_error_handler(self, adv, err):
logging.error("error resolving service: %s" % err) adv.set_state(_SA_UNRESOLVED)
logging.error("Error resolving service %s.%s: %s" % (adv.name(), adv.stype(), err))
def _resolve_service(self, adv): def _resolve_service(self, adv):
"""Resolve and lookup a ZeroConf service to obtain its address and TXT records.""" """Resolve and lookup a ZeroConf service to obtain its address and TXT records."""
# Ask avahi to resolve this particular service # Ask avahi to resolve this particular service
logging.debug('resolving service %s %s' % (adv.name(), adv.stype()))
self._mdns_service.ResolveService(int(adv.interface()), int(adv.protocol()), adv.name(), self._mdns_service.ResolveService(int(adv.interface()), int(adv.protocol()), adv.name(),
adv.stype(), adv.domain(), avahi.PROTO_UNSPEC, dbus.UInt32(0), adv.stype(), adv.domain(), avahi.PROTO_UNSPEC, dbus.UInt32(0),
reply_handler=self._resolve_service_reply_cb_glue, reply_handler=lambda *args: self._resolve_service_reply_cb_glue(adv, *args),
error_handler=self._resolve_service_error_handler) error_handler=lambda *args: self._resolve_service_error_handler(adv, *args))
return False return False
def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags): def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags):
logging.debug("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (full_name, flags, stype, domain, interface, protocol)) local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
# Add the service to our unresolved services list
adv_list = self._find_service_adv(interface=interface, protocol=protocol, adv_list = self._find_service_adv(interface=interface, protocol=protocol,
name=full_name, stype=stype, domain=domain) name=full_name, stype=stype, domain=domain, local=local)
adv = None adv = None
if not adv_list: if not adv_list:
adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name, adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name,
stype=stype, domain=domain) stype=stype, domain=domain, local=local)
self._service_advs.append(adv) self._service_advs.append(adv)
else: else:
adv = adv_list[0] adv = adv_list[0]
# Find out the IP address of this interface, if we haven't already
if interface not in self._local_addrs.keys():
ifname = self._mdns_service.GetNetworkInterfaceNameByIndex(interface)
if ifname:
addr = _get_local_ip_address(ifname)
if addr:
self._local_addrs[interface] = addr
# Decompose service name if we can # Decompose service name if we can
(actid, buddy_name) = Service.decompose_service_name(full_name) (actid, buddy_name) = Service.decompose_service_name(full_name)
@ -508,11 +485,12 @@ class PresenceService(object):
resolve = False resolve = False
if actid is not None or stype in self._registered_service_types: if actid is not None or stype in self._registered_service_types:
resolve = True resolve = True
if resolve and not adv in self._resolve_queue: if resolve and adv.state() == _SA_UNRESOLVED:
self._resolve_queue.append(adv) logging.debug("Found '%s' (%d) of type '%s' in domain" \
" '%s' on %i.%i; will resolve." % (full_name, flags, stype,
domain, interface, protocol))
adv.set_state(_SA_RESOLVE_PENDING)
gobject.idle_add(self._resolve_service, adv) gobject.idle_add(self._resolve_service, adv)
else:
logging.debug("Do not resolve service '%s' of type '%s', we don't care about it." % (full_name, stype))
return False return False
@ -520,22 +498,27 @@ class PresenceService(object):
gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags) gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags)
def _service_disappeared_cb(self, interface, protocol, full_name, stype, domain, flags): def _service_disappeared_cb(self, interface, protocol, full_name, stype, domain, flags):
logging.debug("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (full_name, stype, domain, interface, protocol)) local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
# If it's an unresolved service, remove it from our unresolved list # If it's an unresolved service, remove it from our unresolved list
adv_list = self._find_service_adv(interface=interface, protocol=protocol, adv_list = self._find_service_adv(interface=interface, protocol=protocol,
name=full_name, stype=stype, domain=domain) name=full_name, stype=stype, domain=domain, local=local)
if not adv_list: if not adv_list:
return False return False
# Get the service object; if none, we have nothing left to do # Get the service object; if none, we have nothing left to do
adv = adv_list[0] adv = adv_list[0]
if adv in self._resolve_queue:
self._resolve_queue.remove(adv)
service = adv.service() service = adv.service()
self._service_advs.remove(adv)
del adv
if not service: if not service:
return False return False
logging.debug("Service %s.%s on %i.%i disappeared." % (full_name,
stype, domain, interface, protocol))
self._dbus_helper.ServiceDisappeared(service.object_path())
self._handle_remove_activity_service(service)
# Decompose service name if we can # Decompose service name if we can
(actid, buddy_name) = Service.decompose_service_name(full_name) (actid, buddy_name) = Service.decompose_service_name(full_name)
@ -546,8 +529,6 @@ class PresenceService(object):
pass pass
else: else:
buddy.remove_service(service) buddy.remove_service(service)
self._dbus_helper.ServiceDisappeared(service.object_path())
self._handle_remove_activity_service(service)
if not buddy.is_valid(): if not buddy.is_valid():
self._dbus_helper.BuddyDisappeared(buddy.object_path()) self._dbus_helper.BuddyDisappeared(buddy.object_path())
del self._buddies[buddy_name] del self._buddies[buddy_name]
@ -567,7 +548,6 @@ class PresenceService(object):
s_browser = self._mdns_service.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0)) s_browser = self._mdns_service.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0))
browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, s_browser), browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, s_browser),
avahi.DBUS_INTERFACE_SERVICE_BROWSER) avahi.DBUS_INTERFACE_SERVICE_BROWSER)
logging.debug("now browsing for services of type '%s' in domain '%s' on %i.%i ..." % (stype, domain, interface, protocol))
browser_obj.connect_to_signal('ItemNew', self._service_appeared_cb_glue) browser_obj.connect_to_signal('ItemNew', self._service_appeared_cb_glue)
browser_obj.connect_to_signal('ItemRemove', self._service_disappeared_cb_glue) browser_obj.connect_to_signal('ItemRemove', self._service_disappeared_cb_glue)
@ -600,7 +580,7 @@ class PresenceService(object):
raise Exception("Avahi does not appear to be running. '%s'" % str_exc) raise Exception("Avahi does not appear to be running. '%s'" % str_exc)
else: else:
raise exc raise exc
logging.debug("now browsing domain '%s' on %i.%i ..." % (domain, interface, protocol)) logging.debug("Browsing domain '%s' on %i.%i ..." % (domain, interface, protocol))
browser_obj.connect_to_signal('ItemNew', self._new_service_type_cb_glue) browser_obj.connect_to_signal('ItemNew', self._new_service_type_cb_glue)
self._service_type_browsers[(interface, protocol, domain)] = browser_obj self._service_type_browsers[(interface, protocol, domain)] = browser_obj
return False return False
@ -667,7 +647,7 @@ class PresenceService(object):
objid = self._get_next_object_id() objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=name, service = Service.Service(self._bus_name, objid, name=name,
stype=stype, domain=domain, address=address, port=port, stype=stype, domain=domain, address=address, port=port,
properties=properties, source_address=None) properties=properties, source_address=None, local=True)
self._services[(name, stype)] = service self._services[(name, stype)] = service
port = service.get_port() port = service.get_port()

View File

@ -104,7 +104,8 @@ class Service(object):
"""Encapsulates information about a specific ZeroConf/mDNS """Encapsulates information about a specific ZeroConf/mDNS
service as advertised on the network.""" service as advertised on the network."""
def __init__(self, bus_name, object_id, name, stype, domain=u"local", def __init__(self, bus_name, object_id, name, stype, domain=u"local",
address=None, port=-1, properties=None, source_address=None): address=None, port=-1, properties=None, source_address=None,
local=False):
if not bus_name: if not bus_name:
raise ValueError("DBus bus name must be valid") raise ValueError("DBus bus name must be valid")
if not object_id or type(object_id) != type(1): if not object_id or type(object_id) != type(1):
@ -138,6 +139,7 @@ class Service(object):
self._properties = {} self._properties = {}
self.set_properties(properties) self.set_properties(properties)
self._avahi_entry_group = None self._avahi_entry_group = None
self._local = local
# Source address is the unicast source IP # Source address is the unicast source IP
self._source_address = None self._source_address = None
@ -180,6 +182,9 @@ class Service(object):
raise RuntimeError("Can only set a service's owner once") raise RuntimeError("Can only set a service's owner once")
self._owner = owner self._owner = owner
def is_local(self):
return self._local
def get_name(self): def get_name(self):
"""Return the service's name, usually that of the """Return the service's name, usually that of the
buddy who provides it.""" buddy who provides it."""