Rework PS owner service handling to use avahi flags for local services
This commit is contained in:
parent
4ae80802ce
commit
f205e8c67b
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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."""
|
||||
|
Loading…
Reference in New Issue
Block a user