Friday, July 11, 2014

GSoC Report #2 - Avahi network service discover (GNOME Keysign)

Hi !

Last time I had talked about the design of the GUI for Keysign application and how will this app be used to automate key signing process. I ended up with telling you that I had started to work on the network part of the application and that I used avahi to discover services on the network.

Basically, in order to transfer data between two computers we have to implement a client-server model:

  •  The client is the Keysign app found on the computer of the person who will sign a key. This will make a request on the ip address and port discovered by avahi.
  •  The server is the Keysign app that is found on the person's laptop who wants to get his key signed. When that user selects a key and present it to be signed, it will start up the server that listens for tcp connection.

Now here comes the avahi ' s job, which is to allows us to publish and discover services and hosts running on the local network.
This simple scenario involves only two users connected on the same WIFI network. Later on it might be best to open a secured channel and have all key signing participans connected to it.
Right now, after we 'll establish a connection, the public key can be transferred insecurely but authenticated by using the owner's key fingerprint.

Here is a simplistic example of an event-driven Avahi Publisher using avahi and dbus libraries for python:


  1. class AvahiPublisher:
  2.     def __init__(self, name, port, stype="_demo._tcp",
  3.                  domain="", host="", text=""):
  4.         # simple instantiation
  5.     def publish(self):
  6.         bus = dbus.SystemBus()
  7.         server = dbus.Interface(bus.get_object(
  8.                                     avahi.DBUS_NAME,
  9.                                     avahi.DBUS_PATH_SERVER),
  10.                                 avahi.DBUS_INTERFACE_SERVER)
  11.         group = dbus.Interface(
  12.                     bus.get_object(avahi.DBUS_NAME,
  13.                                    server.EntryGroupNew()),
  14.                     avahi.DBUS_INTERFACE_ENTRY_GROUP)

  15.         group.AddService(avahi.IF_UNSPEC, 
  16.                      avahi.PROTO_UNSPEC,dbus.UInt32(0),
  17.                      self.name, self.stype, self.domain, self.host,
  18.                      dbus.UInt16(self.port), self.text)
  19.         group.Commit()
  20.         self.group = group
  21.     def unpublish(self):
  22.         self.group.Reset()


For this to work you need the avahi D-Bus API that uses avahi-daemon and has bindings for other languages than C.

The problem that I had encountered was with the avahi service browsing. D-Bus connections require a main loop to  make asynchronous calls, receive signals or export objects so that whenever a new service is published or unpublished, avahi discovers it and calls the handler method.

The thing is that the default main loop of the Gtk+ application doesn't work with DBus. It seems that DBus is expecting a DBusGMainLoop , not an ordinary loop.
Not only this but the library wraps dbus-glib rather then gio's dbus implementation which we wanted to use.

For now we will stick with these rather old bindings if we want to use avahi. As a temporary solution , we use both GObject Introspection and python-dbus. It is bad but I lack the knowledge for how to make it work in other way.

This is how the service discovering is handled:


  1. class AvahiBrowser(GObject.GObject):
  2.     __gsignals__ = {
  3.         'new_service'(GObject.SIGNAL_RUN_LAST, None,
  4.             # name, address (could be an int too (for IPv4)), port
  5.             (str, str, int))
  6.     }
  7.     def __init__(self, loop=None, service='_._tcp'):
  8.         GObject.GObject.__init__(self)
  9.         self.service = service
  10.         # It seems that these are different loops..?!
  11.         self.loop = loop or DBusGMainLoop()
  12.         self.bus = dbus.SystemBus(mainloop=self.loop)
  13.         self.server = dbus.Interface( self.bus.get_object(
  14.                avahi.DBUS_NAME, '/')'org.freedesktop.Avahi.Server')
  15.         self.sbrowser = dbus.Interface(self.bus.get_object(
  16.             avahi.DBUS_NAME,self.server.ServiceBrowserNew(avahi.IF_UNSPEC,
  17.                 avahi.PROTO_UNSPEC, TYPE, 'local', dbus.UInt32(0))),
  18.             avahi.DBUS_INTERFACE_SERVICE_BROWSER)
  19.         self.sbrowser.connect_to_signal("ItemNew", self.on_new_item)

  20.     def on_new_item(self, interface, protocol, name, stype, 
  21.                    domain, flags):
  22.         print "Found service '%s' type '%s' domain '%s' " 
  23.                % (name, stype, domain)
  24.         self.server.ResolveService(interface, protocol, name, stype,
  25.             domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
  26.             reply_handler=self.on_service_resolved,
  27.             error_handler=self.on_error)


I want to thank my mentor Tobi for helping me on this part, as there were some things a bit too complex for me.
In any case, the app now publishes itself on the network and browse for services until it is stopped. It is not handled properly yet , as to only publish the service only when a key is available to be transferred, but work is in progress.

From now on I will post more frequently as I have done some work but did not post about it yet.
I hope you enjoyed reading this. See you soon :-)