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 representing the owner of the machine. This is the client
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)
self._nick_name = nick
self._nick_name = env.get_nick_name()
self._color = env.get_color()
self._ps = ps
@ -303,9 +303,12 @@ class Owner(Buddy):
# service added to the Owner determines the owner's address
source_addr = service.get_source_address()
if self._address is None:
if self._ps.is_local_ip_address(source_addr):
if service.is_local():
self._address = source_addr
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)
def is_owner(self):

View File

@ -4,32 +4,15 @@ import Service
import Activity
import random
import logging
from sugar import env
from sugar import util
def _get_local_ip_address(ifname):
"""Call Linux specific bits to retrieve our own IP address."""
import socket
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
_SA_UNRESOLVED = 0
_SA_RESOLVE_PENDING = 1
_SA_RESOLVED = 2
class ServiceAdv(object):
"""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._protocol = protocol
if type(name) != type(u""):
@ -42,7 +25,10 @@ class ServiceAdv(object):
raise ValueError("service advertisement domain must be unicode.")
self._domain = domain
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):
return self._interface
@ -54,16 +40,21 @@ class ServiceAdv(object):
return self._stype
def domain(self):
return self._domain
def is_local(self):
return self._local
def service(self):
return self._service
def set_service(self, service):
if not isinstance(service, Service.Service):
raise ValueError("must be a valid service.")
self._service = service
def resolved(self):
return self._resolved
def set_resolved(self, resolved):
self._resolved = resolved
def state(self):
return self._state
def set_state(self, state):
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"
@ -246,10 +237,9 @@ class PresenceService(object):
self._services = {} # (name, type) -> Service
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_browsers = {}
self._resolve_queue = [] # Track resolve requests
# Resolved service list
self._service_advs = []
@ -263,10 +253,9 @@ class PresenceService(object):
self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name)
# Our owner object
owner_nick = env.get_nick_name()
objid = self._get_next_object_id()
self._owner = Buddy.Owner(self, self._bus_name, objid, owner_nick)
self._buddies[owner_nick] = self._owner
self._owner = Buddy.Owner(self, self._bus_name, objid)
self._buddies[self._owner.get_name()] = self._owner
self._started = False
@ -341,13 +330,10 @@ class PresenceService(object):
def get_owner(self):
return self._owner
def is_local_ip_address(self, address):
if address in self._local_addrs.values():
return True
return False
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."""
def _find_service_adv(self, interface=None, protocol=None, name=None,
stype=None, domain=None, local=None):
"""Search a list of service advertisements for ones matching
certain criteria."""
adv_list = []
for adv in self._service_advs:
if interface and adv.interface() != interface:
@ -360,10 +346,12 @@ class PresenceService(object):
continue
if domain and adv.domain() != domain:
continue
if local is not None and adv.is_local() != local:
continue
adv_list.append(adv)
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."""
# 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
@ -420,21 +408,17 @@ class PresenceService(object):
self._dbus_helper.ActivityDisappeared(activity.object_path())
del self._activities[actid]
def _resolve_service_reply_cb(self, interface, protocol, full_name, stype, domain, host, aprotocol, address, port, txt, flags):
"""When the service discovery finally gets here, we've got enough 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))
def _resolve_service_reply_cb(self, adv, interface, protocol, full_name,
stype, domain, host, aprotocol, address, port, txt, flags):
"""When the service discovery finally gets here, we've got enough
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
# unresolved list
adv_list = self._find_service_adv(interface=interface, protocol=protocol,
name=full_name, stype=stype, domain=domain)
if not adv_list:
if not adv in self._service_advs:
return False
if adv.state() != _SA_RESOLVED:
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
key = (full_name, stype)
@ -442,12 +426,12 @@ class PresenceService(object):
objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=full_name,
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
else:
# Already tracking this service; likely we were the one that shared it
# in the first place, and therefore the source address would have been
# set yet
# in the first place, and therefore the source address would not have
# been set yet
service = self._services[key]
if not service.get_source_address():
service.set_source_address(address)
@ -456,51 +440,44 @@ class PresenceService(object):
adv.set_service(service)
# 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():
self._handle_new_activity_service(service)
return False
def _resolve_service_reply_cb_glue(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
gobject.idle_add(self._resolve_service_reply_cb, interface, protocol,
name, stype, domain, host, aprotocol, address, port, txt, flags)
def _resolve_service_reply_cb_glue(self, adv, interface, protocol, 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):
logging.error("error resolving service: %s" % err)
def _resolve_service_error_handler(self, adv, err):
adv.set_state(_SA_UNRESOLVED)
logging.error("Error resolving service %s.%s: %s" % (adv.name(), adv.stype(), err))
def _resolve_service(self, adv):
"""Resolve and lookup a ZeroConf service to obtain its address and TXT records."""
# 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(),
adv.stype(), adv.domain(), avahi.PROTO_UNSPEC, dbus.UInt32(0),
reply_handler=self._resolve_service_reply_cb_glue,
error_handler=self._resolve_service_error_handler)
reply_handler=lambda *args: self._resolve_service_reply_cb_glue(adv, *args),
error_handler=lambda *args: self._resolve_service_error_handler(adv, *args))
return False
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))
# Add the service to our unresolved services list
local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
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
if not adv_list:
adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name,
stype=stype, domain=domain)
stype=stype, domain=domain, local=local)
self._service_advs.append(adv)
else:
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
(actid, buddy_name) = Service.decompose_service_name(full_name)
@ -508,11 +485,12 @@ class PresenceService(object):
resolve = False
if actid is not None or stype in self._registered_service_types:
resolve = True
if resolve and not adv in self._resolve_queue:
self._resolve_queue.append(adv)
if resolve and adv.state() == _SA_UNRESOLVED:
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)
else:
logging.debug("Do not resolve service '%s' of type '%s', we don't care about it." % (full_name, stype))
return False
@ -520,22 +498,27 @@ class PresenceService(object):
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):
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
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:
return False
# Get the service object; if none, we have nothing left to do
adv = adv_list[0]
if adv in self._resolve_queue:
self._resolve_queue.remove(adv)
service = adv.service()
self._service_advs.remove(adv)
del adv
if not service:
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
(actid, buddy_name) = Service.decompose_service_name(full_name)
@ -546,8 +529,6 @@ class PresenceService(object):
pass
else:
buddy.remove_service(service)
self._dbus_helper.ServiceDisappeared(service.object_path())
self._handle_remove_activity_service(service)
if not buddy.is_valid():
self._dbus_helper.BuddyDisappeared(buddy.object_path())
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))
browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, s_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('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)
else:
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)
self._service_type_browsers[(interface, protocol, domain)] = browser_obj
return False
@ -667,7 +647,7 @@ class PresenceService(object):
objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=name,
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
port = service.get_port()

View File

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